/*
* This file is part of the Jikes RVM project (http://jikesrvm.org).
*
* This file is licensed to You under the Common Public License (CPL);
* You may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.opensource.org/licenses/cpl1.0.php
*
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.
*/
package org.jikesrvm.scheduler.greenthreads;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.PrintStream;
import org.jikesrvm.VM;
import org.jikesrvm.VM_Callbacks;
import org.jikesrvm.runtime.VM_Magic;
import static org.jikesrvm.runtime.VM_SysCall.sysCall;
import org.jikesrvm.runtime.VM_Time;
import org.jikesrvm.runtime.VM_TimeoutException;
import org.jikesrvm.util.VM_StringUtilities;
import org.vmmagic.pragma.Inline;
/**
* Interface to filesystem of underlying operating system.
* These methods use nonblocking I/O for reads and writes and, if necessary,
* use the VM_Processor's IO queue (via the VM_Wait.ioWaitRead()
* and VM_Wait.ioWaitWrite() methods) to suspend the calling thread
* until the underlying file descriptor is ready.
*
* <p> Originally, some of these methods returned a special value
* to indicate that the operation would block. Because all<sup>*</sup>
* file descriptors are nonblocking now, this is no longer necessary.
*
* <p> <sup>*</sup> due to Unix brain damage, we can not actually
* make every file descriptor nonblocking. The problem is for stdin,
* stdout, and stderr, which we must sometimes share with other
* processes. The <code>O_NONBLOCK</code> flag is a sticky property
* of the <em>file</em>, not the file descriptor. Hence, every
* file descriptor sharing the same file (even those copied with
* <code>dup()</code> or <code>fork()</code>) shares the blocking
* mode. Thus, if we make stdin nonblocking, and stdin is a tty shared
* with another process, we will hose the other process.
*
* <p> The current "solution" for this problem is to special case
* file descriptors 0, 1, and 2 for reads and writes. When they are not
* connected to a tty, we assume they're private, and set them to nonblocking.
* If they are connected to a tty, then we kluge our read and write
* functions to perform a preemptive IO wait, which hopefully prevents
* them from blocking in the OS.
*/
public class VM_FileSystem {
// options for open()
public static final int OPEN_READ = 0; // open for read/only access
public static final int OPEN_WRITE = 1; // open for read/write access, create if doesn't already exist,
// truncate if already exists
public static final int OPEN_MODIFY = 2; // open for read/write access, create if doesn't already exist
public static final int OPEN_APPEND = 3; // open for read/write access, create if doesn't already exist, append writes
// options for seek()
public static final int SEEK_SET = 0; // set i/o position to start of file plus "offset"
public static final int SEEK_CUR = 1; // set i/o position to current position plus "offset"
public static final int SEEK_END = 2; // set i/o position to end of file plus "offset"
// options for stat()
public static final int STAT_EXISTS = 0;
public static final int STAT_IS_FILE = 1;
public static final int STAT_IS_DIRECTORY = 2;
public static final int STAT_IS_READABLE = 3;
public static final int STAT_IS_WRITABLE = 4;
public static final int STAT_LAST_MODIFIED = 5;
public static final int STAT_LENGTH = 6;
// options for access()
public static final int ACCESS_F_OK = 00;
public static final int ACCESS_R_OK = 04;
public static final int ACCESS_W_OK = 02;
public static final int ACCESS_X_OK = 01;
/**
* Keep track of whether or not we were able to make
* the stdin, stdout, and stderr file descriptors nonblocking.
* By default, we assume that these descriptors ARE blocking,
* and we kluge around this problem when we read or write them.
*/
private static boolean[] standardFdIsNonblocking = new boolean[3];
/**
* Get file status.
* @param fileName file name
* @param kind kind of info desired (one of STAT_XXX, above)
* @return desired info (-1 -> error)
* The boolean ones return 0 in case of non-true, 1 in case of
* true status.
*/
public static int stat(String fileName, int kind) {
// convert file name from unicode to filesystem character set
// (assume file name is ascii, for now)
byte[] asciiName = VM_StringUtilities.stringToBytesNullTerminated(fileName);
int rc = sysCall.sysStat(asciiName, kind);
if (VM.TraceFileSystem) VM.sysWrite("VM_FileSystem.stat: name=" + fileName + " kind=" + kind + " rc=" + rc + "\n");
return rc;
}
/**
* Get user's perms for a file.
* @param fileName file name
* @param kind kind of access perm(s) to check for (ACCESS_W_OK,...)
* @return 0 if access ok (-1 -> error)
*/
public static int access(String fileName, int kind) {
// convert file name from unicode to filesystem character set
// (assume file name is ascii, for now)
byte[] asciiName = VM_StringUtilities.stringToBytesNullTerminated(fileName);
int rc = sysCall.sysAccess(asciiName, kind);
if (VM.TraceFileSystem) {
VM.sysWrite("VM_FileSystem.access: name=" + fileName + " kind=" + kind + " rc=" + rc + "\n");
}
return rc;
}
/**
* Is the given fd returned from an ioWaitRead() or ioWaitWrite()
* ready?
*/
@Inline
private static boolean isFdReady(int fd) {
return (fd & VM_ThreadIOConstants.FD_READY_BIT) != 0;
}
/**
* Hack to cope with the fact that we sometimes cannot
* make the standard file descriptors nonblocking.
* If called on the stdin file descriptor when we couldn't
* set it to nonblocking, it will try to block the calling
* thread until the file descriptor is ready (so the read
* will hopefully complete without blocking).
*
* @return true if the wait was successful and the file descriptor
* is ready, or false if an error occurred (and the read should
* be avoided)
*/
@Inline
private static boolean blockingReadHack(int fd) {
if (fd >= 3 || standardFdIsNonblocking[fd]) {
return true;
}
VM_ThreadIOWaitData waitData = VM_Wait.ioWaitRead(fd);
return isFdReady(waitData.readFds[0]);
}
/**
* Hack to cope with the fact that we sometimes cannot
* make the standard file descriptors nonblocking.
* If called on the stdout or stderr file descriptors when we couldn't
* set them to nonblocking, it will try to block the calling
* thread until the file descriptor is ready (so the write
* will hopefully complete without blocking).
*
* @return true if the wait was successful and the file descriptor
* is ready, or false if an error occurred (and the write should
* be avoided)
*/
@Inline
private static boolean blockingWriteHack(int fd) {
if (fd >= 3 || standardFdIsNonblocking[fd]) {
return true;
}
VM_ThreadIOWaitData waitData = VM_Wait.ioWaitWrite(fd);
return isFdReady(waitData.writeFds[0]);
}
/**
* Read single byte from file.
* FIXME: should throw an IOException to indicate an error?
*
* @param fd file descriptor
* @return byte that was read (< -1: i/o error, -1: eof, >= 0: data)
*/
public static int readByte(int fd) {
if (!blockingReadHack(fd)) {
return -2;
}
// See readBytes() method for an explanation of how the read loop works.
for (; ;) {
int b = sysCall.sysReadByte(fd);
if (b >= -1) {
// Either a valid read, or we reached EOF
return b;
} else if (b == -2) {
// Operation would have blocked
VM_ThreadIOWaitData waitData = VM_Wait.ioWaitRead(fd);
if (!isFdReady(waitData.readFds[0])) {
// Hmm, the wait returned, but the fd is not ready.
// Assume an error was detected (such as the fd becoming invalid).
return -2;
}
} else {
// Read returned with a genuine error
return -2;
}
}
}
/**
* Write single byte to file.
* FIXME: should throw an IOException to indicate an error?
*
* @param fd file descriptor
* @param b byte to be written
* @return -1: i/o error
*/
public static int writeByte(int fd, int b) {
if (!blockingWriteHack(fd)) {
return -1;
}
// See writeBytes() for an explanation of how the write loop works
for (; ;) {
int rc = sysCall.sysWriteByte(fd, b);
if (rc == 0) {
return 0; // success
} else if (rc == -2) {
// Write would have blocked.
VM_ThreadIOWaitData waitData = VM_Wait.ioWaitWrite(fd);
if (!isFdReady(waitData.writeFds[0])) {
// Looks like an error occurred.
return -1;
}
// Fd looks like it's ready, so retry the write
} else {
// The write returned with an error.
return -1;
}
}
}
/**
* Version of <code>readBytes()</code> which does not support timeouts.
* Will block indefinitely until data can be read.
* @see #readBytes(int,byte[],int,int,double)
*/
public static int readBytes(int fd, byte[] buf, int off, int cnt) {
try {
return readBytes(fd, buf, off, cnt, VM_ThreadEventConstants.WAIT_INFINITE);
} catch (VM_TimeoutException e) {
if (VM.VerifyAssertions) VM._assert(false); // impossible
return -2;
}
}
/**
* Read multiple bytes from file or socket.
* FIXME: should throw an IOException to indicate an error?
*
* @param fd file or socket descriptor
* @param buf buffer to be filled
* @param off position in buffer
* @param cnt number of bytes to read
* @param totalWaitTime number of seconds caller is willing to wait
* @return number of bytes read (-2: error)
* @throws VM_TimeoutException if the read times out
*/
public static int readBytes(int fd, byte[] buf, int off, int cnt, double totalWaitTime) throws VM_TimeoutException {
if (off < 0) {
throw new IndexOutOfBoundsException();
}
// trim request to fit array
// note: this behavior is the way the JDK does it (as of version 1.1.3)
// whereas the language spec says to throw IndexOutOfBounds exception...
//
if (off + cnt > buf.length) {
cnt = buf.length - off;
}
// The canonical read loop. Try the read repeatedly until either
// - it succeeds,
// - it returns EOF, or
// - it returns with an error.
// If the read fails because it would have blocked, then
// put this thread on the IO queue, then try again if it
// looks like the fd is ready.
boolean hasTimeout = (totalWaitTime >= 0.0);
double lastWaitTime = hasTimeout ? now() : 0.0;
if (!blockingReadHack(fd)) {
return -2;
}
int read = 0;
for (; ;) {
int rc = sysCall.sysReadBytes(fd, VM_Magic.objectAsAddress(buf).plus(off), cnt);
if (rc == 0) {
// EOF
return read;
} else if (rc > 0) {
// Read succeeded, perhaps partially
read += rc;
off += rc;
cnt -= rc;
if (cnt == 0) return read;
// did not get everything, let's try again
} else if (rc == -1) {
// last read would have blocked
// perhaps we have read some stuff already, if so, return just that
if (read != 0) {
return read;
}
// Put thread on IO wait queue
if (VM.VerifyAssertions) VM._assert(!hasTimeout || totalWaitTime >= 0.0);
VM_ThreadIOWaitData waitData = VM_Wait.ioWaitRead(fd, totalWaitTime);
// Did the wait time out?
if (waitData.isTimedOut()) {
throw new VM_TimeoutException("read timed out");
}
// Did the file descriptor become ready?
if (!isFdReady(waitData.readFds[0])) {
// Fd not ready; presumably an error was detected while on the IO queue
return -2;
} else {
// Fd appears to be ready, so update the wait time (if necessary)
// and try the read again.
if (hasTimeout) {
double now = now();
totalWaitTime -= (now - lastWaitTime);
if (totalWaitTime < 0.0) {
throw new VM_TimeoutException("read timed out");
}
lastWaitTime = now;
}
}
} else {
// Read returned an error
return -2;
}
}
}
private static double now() {
return ((double) VM_Time.currentTimeMillis()) / 1000;
}
/**
* Write multiple bytes to file or socket.
* FIXME: should throw an IOException to indicate an error?
*
* @param fd file or socket descriptor
* @param buf buffer to be written
* @param off position in buffer
* @param cnt number of bytes to write
* @return number of bytes written (-2: error)
*/
public static int writeBytes(int fd, byte[] buf, int off, int cnt) {
if (cnt == 0) return 0;
if (off < 0) {
throw new IndexOutOfBoundsException();
}
// trim request to fit array
// note: this behavior is the way the JDK does it (as of version 1.1.3)
// whereas the language spec says to throw IndexOutOfBounds exception...
//
if (off + cnt > buf.length) {
cnt = buf.length - off;
}
// The canonical write loop. Try the write repeatedly until
// - it succeeds, or
// - it returns an error
// If the write would have blocked, put this thread on the
// IO queue, then try again if it looks like the fd is ready.
if (!blockingWriteHack(fd)) {
return -2;
}
int written = 0;
for (; ;) {
int rc = sysCall.sysWriteBytes(fd, VM_Magic.objectAsAddress(buf).plus(off), cnt);
if (rc >= 0) {
// Write succeeded, perhaps partially
written += rc;
off += rc;
cnt -= rc;
if (cnt == 0) return written;
} else if (rc == -1) {
// Write would have blocked
VM_ThreadIOWaitData waitData = VM_Wait.ioWaitWrite(fd);
if (!isFdReady(waitData.writeFds[0])) {
// Fd is not ready, so presumably an error occurred while on IO queue
return -2;
}
// Fd apprears to be ready, so try write again
} else {
// Write returned with an error
return -2;
}
}
}
public static boolean sync(int fd) {
return sysCall.sysSyncFile(fd) == 0;
}
public static int bytesAvailable(int fd) {
return sysCall.sysBytesAvailable(fd);
}
/**
* File descriptor registration hook.
* All (valid) FileDescriptor objects created in the system should
* pass their underlying OS file descriptors to this method,
* so we can set them to nonblocking mode.
*
* <p>No file descriptors should be created before the
* VM is booted sufficiently that thread switching is enabled.
* The reason is that all Java streams use nonblocking file descriptors,
* and we use the VM rather than the OS to block threads until their
* streams are ready.
*
* @param fd the file descriptor
* @param shared true if the file descriptor will be shared,
* false otherwise; we set the close-on-exec flag for non-shared fds,
* to prevent child processes from accessing them
*/
public static void onCreateFileDescriptor(int fd, boolean shared) {
// The most likely reason for this assertion to be triggered is that
// some code caused class loading before VM_Scheduler.boot()
// finished initializing the virtual processors. It should generally
// be possible to move such code later in the initialization sequence.
if (VM.VerifyAssertions) {
VM._assert(VM_GreenScheduler.allProcessorsInitialized, "fd used before system is fully booted\n");
}
int rc;
// Set the file descriptor to be nonblocking.
rc = sysCall.sysNetSocketNoBlock(fd, 1);
if (rc < 0) {
VM.sysWrite("VM: warning: could not set file descriptor " + fd + " to nonblocking\n");
}
// Note: it is conceivable that file descriptor 0, 1, or 2 could
// get passed to this method. For example, a program could close
// stdin, then open a new FileInputStream, which might get assigned
// fd 0. Unlikely, but possible. If this does happen, it's OK for
// us to make the file descriptor nonblocking, because we don't share
// such file descriptors with other processes.
if (fd < 3) {
standardFdIsNonblocking[fd] = (rc == 0);
}
// If file descriptor will not be shared, set close-on-exec flag
if (!shared) {
rc = sysCall.sysSetFdCloseOnExec(fd);
if (rc < 0) {
VM.sysWrite("VM: warning: could not set close-on-exec flag " + "for fd " + fd);
}
}
}
/**
* Called from VM.boot to set up java.lang.System.in, java.lang.System.out,
* and java.lang.System.err
*/
public static void initializeStandardStreams() {
FileInputStream fdIn = new FileInputStream(FileDescriptor.in);
FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err);
System.setIn(new BufferedInputStream(fdIn));
System.setOut(new PrintStream(new BufferedOutputStream(fdOut, 128), true));
System.setErr(new PrintStream(new BufferedOutputStream(fdErr, 128), true));
VM_Callbacks.addExitMonitor(new VM_Callbacks.ExitMonitor() {
public void notifyExit(int value) {
try {
System.err.flush();
System.out.flush();
} catch (Throwable e) {
VM.sysWriteln("vm: error flushing stdout, stderr");
}
}
});
}
}