/**
* This file is part of Erjang - A JVM-based Erlang VM
*
* Copyright (c) 2009 by Trifork
*
* 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 erjang.driver.efile;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.channels.SelectableChannel;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.NotLinkException;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.logging.Level;
import java.util.logging.Logger;
import kilim.Pausable;
import erjang.EBinList;
import erjang.EBinary;
import erjang.EHandle;
import erjang.ERT;
import erjang.ERef;
import erjang.EString;
import erjang.NotImplemented;
import erjang.driver.EAsync;
import erjang.driver.EDriverInstance;
import erjang.driver.IO;
/**
* Java does nor support proper non-blocking IO for files (i.e., you cannot
* select on a file). Select is only supported for sockets (and server sockets).
*/
public class EFile extends EDriverInstance {
static Logger log = Logger.getLogger("erjang.driver.file");
public static final String RESOURCE_PREFIX = "/~resource/";
private static Field FileDescriptor_FD;
static {
try {
FileDescriptor_FD = FileDescriptor.class.getDeclaredField("fd");
FileDescriptor_FD.setAccessible(true);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private int getFDnumber(FileDescriptor fd) throws IllegalAccessException {
int res = FileDescriptor_FD.getInt(fd);
// Windows doesn't provide FD numbers - fallback to a usable dummy:
if (res == -1) res = 255;
return res;
}
/**
*
*/
private FileChannel fd;
private int flags;
private TimerState timer_state;
private FileAsync invoke;
private Queue<FileAsync> cq;
private int level;
private Lock q_mtx;
private int write_buffered;
private int write_bufsize;
public int posix_errno;
public boolean write_error;
private long write_delay;
private ByteBuffer read_binp;
protected File name;
/**
*
*/
private final class WriteAsync extends FileAsync {
Lock q_mtx;
int size, free_size, reply_size;
/**
* @param xreplySize
* @param efile
*/
private WriteAsync(boolean reply, int reply_size) {
EFile efile = EFile.this;
super.command = FILE_WRITE;
super.fd = efile.fd;
super.flags = efile.flags;
super.level = 1;
this.q_mtx = efile.q_mtx;
this.size = efile.write_buffered;
super.reply = reply;
this.free_size = 0;
this.reply_size = reply_size;
}
/** invoke_writev */
@Override
public void async() {
int size;
boolean segment = again && this.size >= 2 * FILE_SEGMENT_WRITE;
if (segment) {
size = FILE_SEGMENT_WRITE;
} else {
size = this.size;
}
q_mtx.lock();
ByteBuffer[] iov0 = driver_peekq();
if (iov0 != null) {
// copy the buffers
ByteBuffer[] iov = iov0.clone();
q_mtx.unlock();
// figure out how much data we have available for writing
long p = 0;
int iovcnt = 0;
while (p < size && iovcnt < iov.length) {
p += iov[iovcnt++].remaining();
}
if (iov.length > 0) {
// What is this good for?
assert iov[iovcnt - 1].limit() > p - size;
if ((flags & EFILE_COMPRESSED) == EFILE_COMPRESSED) {
for (int i = 0; i < iovcnt; i++) {
try {
free_size += IO.gzwrite(fd, iov[i]);
super.result_ok = true;
} catch (IOException e) {
posix_errno = IO.exception_to_posix_code(e);
super.result_ok = false;
}
}
} else {
try {
long written_bytes = IO.writev(fd, iov);
if (log.isLoggable(Level.FINER)) {
log.finer(""+EFile.this+" :: wrote "+written_bytes);
}
free_size += written_bytes;
result_ok = true;
} catch (java.nio.channels.NonWritableChannelException e) {
posix_errno = Posix.EBADF;
super.result_ok = false;
} catch (IOException e) {
posix_errno = IO.exception_to_posix_code(e);
super.result_ok = false;
}
}
} else if (iov.length == 0) {
result_ok = true;
}
} else {
q_mtx.unlock();
posix_errno = Posix.EINVAL;
result_ok = false;
}
if (!result_ok) {
again = false;
} else if (!segment) {
again = false;
}
}
// called from the DriverTask
@Override
public void ready() throws Pausable {
if (reply) {
if (!result_ok) {
reply_posix_error(posix_errno);
} else {
reply_Uint(reply_size);
}
} else {
if (!result_ok) {
EFile.this.write_error = true;
EFile.this.posix_errno = posix_errno;
}
}
}
/*
* (non-Javadoc)
*
* @see erjang.driver.efile.FileAsync#deq_free_size()
*/
@Override
public void deq_free_size() {
driver_deq(free_size);
}
}
/**
*
*/
public enum TimerState {
IDLE, AGAIN, WRITE
}
/*
* This structure contains date and time.
*/
public static class Time {
short year; /* (4 digits). */
short month; /* (1..12). */
short day; /* (1..31). */
short hour; /* (0..23). */
short minute; /* (0..59). */
short second; /* (0..59). */
}
/** stat info about file */
public static class Info {
int size_low; /* Size of file, lower 32 bits.. */
int size_high; /* Size of file, higher 32 bits. */
int type; /* Type of file -- one of FT_*. */
int access; /* Access to file -- one of FA_*. */
int mode; /* Access permissions -- bit field. */
int links; /* Number of links to file. */
int major_device; /* Major device or file system. */
int minor_device; /* Minor device (for devices). */
int inode; /* Inode number. */
int uid; /* User id of owner. */
int gid; /* Group id of owner. */
Time accessTime; /* Last time the file was accessed. */
Time modifyTime; /* Last time the file was modified. */
Time cTime; /* Creation time (Windows) or last */
}
/*
* Open modes for efile_openfile().
*/
public static final int EFILE_MODE_READ = 1;
public static final int EFILE_MODE_WRITE = 2; /*
* Implies truncating file when
* used alone.
*/
public static final int EFILE_MODE_READ_WRITE = 3;
public static final int EFILE_MODE_APPEND = 4;
public static final int EFILE_COMPRESSED = 8;
public static final int EFILE_MODE_EXCL = 16; /*
* Special for reopening on
* VxWorks
*/
/*
* Seek modes for efile_seek().
*/
public static final int EFILE_SEEK_SET = 0;
public static final int EFILE_SEEK_CUR = 1;
public static final int EFILE_SEEK_END = 2;
/*
* File types returned by efile_fileinfo().
*/
public static final int FT_DEVICE = 1;
public static final int FT_DIRECTORY = 2;
public static final int FT_REGULAR = 3;
public static final int FT_SYMLINK = 4;
public static final int FT_OTHER = 5;
/*
* Access attributes returned by efile_fileinfo() (the bits can be ORed
* together).
*/
public static final int FA_NONE = 0;
public static final int FA_WRITE = 1;
public static final int FA_READ = 2;
public static final int FA_EXECUTE = 4;
/* commands sent via efile_output(v) */
public static final int FILE_OPEN = 1; /* Essential for startup */
public static final int FILE_READ = 2;
public static final int FILE_LSEEK = 3;
public static final int FILE_WRITE = 4;
public static final int FILE_FSTAT = 5; /* Essential for startup */
public static final int FILE_PWD = 6; /* Essential for startup */
public static final int FILE_READDIR = 7; /* Essential for startup */
public static final int FILE_CHDIR = 8;
public static final int FILE_FSYNC = 9;
public static final int FILE_MKDIR = 10;
public static final int FILE_DELETE = 11;
public static final int FILE_RENAME = 12;
public static final int FILE_RMDIR = 13;
public static final int FILE_TRUNCATE = 14;
public static final int FILE_READ_FILE = 15; /* Essential for startup */
public static final int FILE_WRITE_INFO = 16;
public static final int FILE_LSTAT = 19;
public static final int FILE_READLINK = 20;
public static final int FILE_LINK = 21;
public static final int FILE_SYMLINK = 22;
public static final int FILE_CLOSE = 23;
public static final int FILE_PWRITEV = 24;
public static final int FILE_PREADV = 25;
public static final int FILE_SETOPT = 26;
public static final int FILE_IPREAD = 27;
public static final int FILE_ALTNAME = 28;
public static final int FILE_READ_LINE = 29;
public static final int FILE_FDATASYNC = 30;
public static final int FILE_FADVISE = 31;
/* Return codes */
public static final byte FILE_RESP_OK = 0;
/**
*
*/
private static final byte[] FILE_RESP_OK_HEADER = new byte[]{ FILE_RESP_OK };
public static final byte FILE_RESP_ERROR = 1;
public static final byte FILE_RESP_DATA = 2;
public static final byte FILE_RESP_NUMBER = 3;
public static final byte FILE_RESP_INFO = 4;
public static final byte FILE_RESP_NUMERR = 5;
public static final byte FILE_RESP_LDATA = 6;
public static final byte FILE_RESP_N2DATA = 7;
public static final byte FILE_RESP_EOF = 8;
public static final byte FILE_RESP_FNAME = 9;
public static final byte FILE_RESP_ALL_DATA = 10;
public static final byte FILE_RESP_LFNAME = 11;
private static final byte[] FILE_RESP_ALL_DATA_HEADER = new byte[]{ FILE_RESP_ALL_DATA };
/* Options */
public static final int FILE_OPT_DELAYED_WRITE = 0;
public static final int FILE_OPT_READ_AHEAD = 1;
/* IPREAD variants */
public static final int IPREAD_S32BU_P32BU = 0;
/* POSIX file advises */
public static final int POSIX_FADV_NORMAL = 0;
public static final int POSIX_FADV_RANDOM = 1;
public static final int POSIX_FADV_SEQUENTIAL = 2;
public static final int POSIX_FADV_WILLNEED = 3;
public static final int POSIX_FADV_DONTNEED = 4;
public static final int POSIX_FADV_NOREUSE = 5;
/* Limits */
public static final int FILE_SEGMENT_READ = (256 * 1024);
public static final int FILE_SEGMENT_WRITE = (256 * 1024);
public static final int FILE_TYPE_DEVICE = 0;
public static final int FILE_TYPE_DIRECTORY = 2;
public static final int FILE_TYPE_REGULAR = 3;
public static final int FILE_TYPE_SYMLINK = 4;
public static final int FILE_ACCESS_NONE = 0;
public static final int FILE_ACCESS_WRITE = 1;
public static final int FILE_ACCESS_READ = 2;
public static final int FILE_ACCESS_READ_WRITE = 3;
// private static final String SYS_INFO = null;
private static final int THREAD_SHORT_CIRCUIT;
/** initialize value of thread_short_circuit */
static {
String buf = System.getenv("ERL_EFILE_THREAD_SHORT_CIRCUIT");
if (buf == null) {
THREAD_SHORT_CIRCUIT = 0;
} else {
THREAD_SHORT_CIRCUIT = Integer.parseInt(buf);
}
}
/**
* @param command
* @param driver
*/
public EFile(EString command, Driver driver) {
super(driver);
this.fd = (FileChannel) null;
this.flags = 0;
this.invoke = null;
this.cq = new LinkedList<FileAsync>();
this.timer_state = TimerState.IDLE;
// this.read_bufsize = 0;
this.read_binp = (ByteBuffer) null;
// this.read_offset = 0;
//this.read_size = 0;
this.write_delay = 0L;
this.write_bufsize = 0;
// this.write_error = 0;
this.q_mtx = driver_pdl_create();
this.write_buffered = 0;
}
/**
* @param replySize
* @throws Pausable
*/
public void reply_Uint(int value) throws Pausable {
if (isUnicodeDriverInterface()) {
ByteBuffer response = ByteBuffer.allocate(9);
response.put(FILE_RESP_NUMBER);
response.putLong(value);
driver_output2(response, null);
} else {
ByteBuffer response = ByteBuffer.allocate(4);
response.putInt(value);
driver_output2(response, null);
}
}
public void reply_Uint_error(int value, int posix_error) throws Pausable {
ByteBuffer response = ByteBuffer.allocate(256);
String err = Posix.errno_id(posix_error);
response.putInt(value);
IO.putstr(response, err, false);
driver_output2(response, null);
}
/**
* @param error
* @throws Pausable
*/
public void reply_posix_error(int posix_errno) throws Pausable {
ByteBuffer response = ByteBuffer.allocate(256);
response.put(FILE_RESP_ERROR);
String err = Posix.errno_id(posix_errno);
IO.putstr(response, err, false);
driver_output2(response, null);
}
@Override
protected void flush() throws Pausable {
int r = flush_write(null);
assert (r == 0);
cq_execute();
}
private abstract class SimpleFileAsync extends FileAsync {
protected final File file;
protected final String name;
{
level = 2;
reply = true;
}
/**
* @param path
* @param cmd
*/
public SimpleFileAsync(byte command, String path) {
this.command = command;
this.name = path;
this.file = ERT.newFile(path);
}
@Override
public final void async() {
try {
this.result_ok = false;
run();
} catch (OutOfMemoryError e) {
this.result_ok = false;
posix_errno = Posix.ENOMEM;
} catch (SecurityException e) {
this.result_ok = false;
posix_errno = Posix.EPERM;
} catch (IOException e) {
this.result_ok = false;
posix_errno = IO.exception_to_posix_code(e);
} catch (Throwable e) {
e.printStackTrace();
this.result_ok = false;
posix_errno = Posix.EUNKNOWN;
}
}
/**
* This is what does the real operation
*/
protected abstract void run() throws IOException;
public void ready() throws Pausable {
reply(EFile.this);
}
}
/*
* (non-Javadoc)
*
* @see erjang.driver.EDriverInstance#outputv(java.nio.ByteBuffer[])
*/
@Override
protected void outputv(EHandle caller, ByteBuffer[] ev) throws Pausable {
if (ev.length == 0 || ev[0].remaining() == 0) {
reply_posix_error(Posix.EINVAL);
return;
}
byte command = ev[0].get();
switch (command) {
case FILE_PREADV: {
ByteBuffer evin = flatten(ev);
evin.getInt(); // skip first 4-byte
final int n = evin.getInt();
final long[] offsets = new long[n];
final ByteBuffer[] res_ev = new ByteBuffer[n+1];
for (int i = 1; i < n+1; i++) {
offsets[i-1] = evin.getLong();
int len = (int) (evin.getLong() & 0x7fffffff);
res_ev[i] = ByteBuffer.allocate(len);
if (log.isLoggable(Level.FINER)) {
log.finer(EFile.this+" :: pread "+len+" @ "+offsets[i-1]);
}
}
res_ev[0] = ByteBuffer.allocate(4+4+8*n);
res_ev[0].putInt(0);
res_ev[0].putInt(n);
if (n == 0) {
reply_ev(FILE_RESP_LDATA, offsets, res_ev);
} else {
cq_enq(new FileAsync() {
int cnt;
{
this.level = 1;
this.fd = EFile.this.fd;
this.cnt = 0;
this.result_ok = true;
}
@Override
public void async() {
// TODO: handle CHOP!
for (int i = cnt; i < n; i++) {
ByteBuffer res = res_ev[i+1];
long pos = offsets[i] + res.position();
int rem = res.remaining();
if (rem > 0) {
int bytes_read;
try {
fd.position(pos);
bytes_read = fd.read(res);
if (log.isLoggable(Level.FINER)) {
log.finer(EFile.this +
":: did pread "+ bytes_read+ "@"+pos
+" into res["+i+"]; missing="+res.remaining());
}
} catch (IOException e) {
result_ok = false;
this.posix_errno = IO.exception_to_posix_code(e);
this.again = false;
return;
}
if (bytes_read >= 0 && res.hasRemaining()) {
this.again = true;
return;
}
res_ev[0].putLong(res.position());
cnt += 1;
}
}
this.again = false;
}
@Override
public void ready() throws Pausable {
if (!result_ok) {
reply_posix_error(posix_errno);
} else {
reply_ev(FILE_RESP_LDATA, offsets, res_ev);
}
}
});
}
} break;
case FILE_CLOSE: {
if (ev.length > 1 && ev[0].hasRemaining()) {
reply_posix_error(Posix.EINVAL);
return;
}
ByteBuffer last = ev[ev.length - 1];
if (last.hasRemaining()) {
reply_posix_error(Posix.EINVAL);
return;
}
if (log.isLoggable(Level.FINE))
log.fine(""+this+" :: close");
//TODO: Flush & check.
// Is this check necessary?
/*
if (fd == null) {
reply_posix_error(Posix.BADF);
return;
}
*/
FileAsync d = new FileAsync() {
{
this.fd = EFile.this.fd;
}
public void async() {
try {
fd.close();
result_ok = true;
} catch (IOException e) {
result_ok = false;
posix_errno = IO.exception_to_posix_code(e);
}
}
@Override
public void ready() throws Pausable {
if (result_ok) {
reply_ok();
} else {
reply_posix_error(posix_errno);
}
}
};
cq_enq(d);
break;
}
case FILE_READ: {
int[] errp = new int[1];
if (flush_write_check_error(errp) < 0) {
reply_posix_error(errp[0]);
return;
}
if (ev.length > 1 && ev[0].hasRemaining()) {
reply_posix_error(Posix.EINVAL);
return;
}
ByteBuffer last = ev[ev.length - 1];
if (last.remaining() != 8) {
reply_posix_error(Posix.EINVAL);
return;
}
final long size = last.getLong();
if (size > Integer.MAX_VALUE || size < 0) {
reply_posix_error(Posix.ENOMEM);
return;
}
//TODO: Write-flush & check.
FileAsync d = new FileAsync() {
private ByteBuffer binp = null;
{
super.level = 2;
super.again = true;
this.fd = EFile.this.fd;
}
@Override
public void async() {
// first time only, initialize binp
if (binp == null) {
try {
binp = ByteBuffer.allocate((int) size);
} catch (OutOfMemoryError e) {
result_ok = false;
posix_errno = Posix.ENOMEM;
return;
}
}
if (binp != null && binp.hasRemaining()) {
try {
int want = binp.remaining();
int bytes = fd.read(binp);
if (log.isLoggable(Level.FINER)) {
log.finer
(EFile.this + ":: did read "+bytes+" bytes of "+want);
}
if (bytes == -1) {
result_ok = true;
again = false;
return;
}
if (binp.hasRemaining()) {
again = true;
return;
} else {
result_ok = true;
}
} catch (IOException e) {
result_ok = false;
posix_errno = IO.exception_to_posix_code(e);
}
}
again = false;
}
@Override
public void ready() throws Pausable {
if (!result_ok) {
reply_posix_error(posix_errno);
return;
}
reply_buf(binp);
binp.flip();
}
};
cq_enq(d);
break;
} //break;
case FILE_READ_FILE: {
if (ev.length > 1 && ev[0].hasRemaining()) {
reply_posix_error(Posix.EINVAL);
return;
}
ByteBuffer last = ev[ev.length - 1];
final String name = IO.getstr(last, true);
if (name.length() == 0) {
reply_posix_error(Posix.ENOENT);
return;
}
if (ClassPathResource.isResource(name)) {
EBinary data = ClassPathResource.read_file(name);
if (data == null) {
reply_posix_error(Posix.ENOENT);
} else {
if (isUnicodeDriverInterface()) {
task.output_from_driver(new EBinList(FILE_RESP_ALL_DATA_HEADER, data));
} else {
task.output_from_driver(new EBinList(FILE_RESP_OK_HEADER, data));
}
}
return;
}
FileAsync d = new FileAsync() {
private ByteBuffer binp = null;
private long size = 0;
{
super.level = 2;
super.again = true;
}
@Override
public void async() {
// first time only, initialize binp
if (binp == null) {
File file = ERT.newFile(name);
try {
this.fd = new FileInputStream(file).getChannel();
} catch (FileNotFoundException e) {
this.again = false;
result_ok = false;
posix_errno = fileNotFound_to_posixErrno(file, EFILE_MODE_READ);
this.again = false;
return;
}
this.size = file.length();
if (size > Integer.MAX_VALUE || size < 0) {
result_ok = false;
posix_errno = Posix.ENOMEM;
} else {
try {
binp = ByteBuffer.allocate((int) size);
} catch (OutOfMemoryError e) {
result_ok = false;
posix_errno = Posix.ENOMEM;
}
if (this.size == 0) {
result_ok = true;
this.again = false;
}
}
}
if (binp != null && binp.hasRemaining()) {
try {
int bytes = fd.read(binp);
if (bytes == -1 && binp.hasRemaining()) {
// urgh, file change size under our feet!
result_ok = false;
posix_errno = Posix.EIO;
}
if (binp.hasRemaining()) {
again = true;
return;
} else {
result_ok = true;
}
} catch (IOException e) {
result_ok = false;
posix_errno = IO.exception_to_posix_code(e);
}
}
try {
fd.close();
} catch (IOException e) {
result_ok = false;
posix_errno = IO.exception_to_posix_code(e);
}
again = false;
}
@Override
public void ready() throws Pausable {
if (!result_ok) {
reply_posix_error(posix_errno);
return;
}
binp.flip();
driver_output_binary(isUnicodeDriverInterface() ? FILE_RESP_ALL_DATA_HEADER : FILE_RESP_OK_HEADER, binp);
}
};
cq_enq(d);
break;
}
case FILE_PWRITEV: {
int[] errp = new int[1];
if (lseek_flush_read(errp) < 0) {
reply_posix_error(errp[0]);
return;
}
if (flush_write_check_error(errp) < 0) {
reply_posix_error(errp[0]);
return;
}
final int n = ev[0].getInt();
if (n == 0) {
if (ev.length > 1 || ev[0].hasRemaining()) {
reply_posix_error(Posix.EINVAL);
} else {
reply_Uint(0);
}
return;
}
if (ev[0].remaining() != (8*2*n)) {
reply_posix_error(Posix.EINVAL);
return;
}
final long[] offsets = new long[n];
final long[] sizes = new long[n];
long total = 0;
for (int i = 0; i < n; i++) {
offsets[i] = ev[0].getLong();
sizes[i] = ev[0].getLong();
total += sizes[i];
}
if (total == 0) {
reply_Uint(0);
return;
}
q_mtx.lock();
try {
driver_enqv(ev);
} finally {
q_mtx.unlock();
}
FileAsync d = new FileAsync() {
int cnt = 0;
{
this.level = 1;
this.fd = EFile.this.fd;
}
@Override
public void async() {
q_mtx.lock();
ByteBuffer[] iov0 = driver_peekq();
if (iov0 != null) {
// copy the buffer list
ByteBuffer[] iov = iov0.clone();
q_mtx.unlock();
int ip = 0;
while (cnt < offsets.length && ip < iov.length) {
if (sizes[cnt] == 0)
{ cnt += 1; continue; }
if(!iov[ip].hasRemaining()) { ip++; continue; }
ByteBuffer o = iov[ip];
if (o.remaining() > sizes[cnt]) {
o = o.slice();
o.limit((int) sizes[cnt]);
}
int bytes;
try {
fd.position(offsets[cnt]);
bytes = fd.write(o);
if (log.isLoggable(Level.FINE)) {
log.fine(""+EFile.this+" :: wrote "+bytes);
}
} catch (IOException e) {
EFile.this.posix_errno = IO.exception_to_posix_code(e);
this.result_ok = false;
return;
}
offsets[cnt] += bytes;
sizes[cnt] -= bytes;
if (o.hasRemaining())
{
this.again = true;
return;
}
if (sizes[cnt] == 0) {
cnt += 1;
}
}
if (cnt != n) {
this.result_ok = false;
EFile.this.posix_errno = Posix.EINVAL;
this.again = false;
} else {
this.again = false;
this.result_ok = true;
}
}
}
@Override
public void ready() throws Pausable {
if (!result_ok) {
reply_Uint_error(cnt, EFile.this.posix_errno);
} else {
reply_Uint(n);
}
}
};
cq_enq(d);
break;
}
case FILE_WRITE: {
int[] errp;
int reply_size = 0;
for (int i = 0; i < ev.length; i++) {
reply_size += ev[i].remaining();
if (log.isLoggable(Level.FINER))
log.finer(""+this+" :: write "+ev[i].remaining());
}
q_mtx.lock();
driver_enqv(ev);
write_buffered += reply_size;
if (write_buffered < write_bufsize) {
q_mtx.unlock();
reply_Uint(reply_size);
if (timer_state == TimerState.IDLE) {
timer_state = TimerState.WRITE;
driver_set_timer(write_delay);
}
} else if (async_write(errp = new int[1], true, reply_size) != 0){
reply_posix_error(errp[0]);
q_mtx.unlock();
} else {
q_mtx.unlock();
}
break;
}
default:
// undo the get() we did to find command
ev[0].position(ev[0].position() - 1);
output(caller, flatten(ev));
}
cq_execute();
}
private int flush_write_check_error(int[] errp) {
int r;
if ( (r = flush_write(errp)) != 0) {
check_write_error(null);
return r;
} else {
return check_write_error(errp);
}
}
private int check_write_error(int[] errp) {
if (write_error) {
if (errp != null) {
errp[0] = this.posix_errno;
}
return -1;
}
return 0;
}
void flush_read() {
this.read_binp = null;
}
private int lseek_flush_read(int[] errp) {
int r = 0;
int read_size = (read_binp == null ? 0 : read_binp.remaining());
flush_read();
if (read_size != 0) {
if ((r = async_lseek(errp, false, -read_size, EFILE_SEEK_CUR)) < 0) {
return r;
}
}
return r;
}
private int async_lseek(int[] errp, final boolean do_reply, final long off, final int whence) {
try {
FileAsync d = new FileAsync() {
{
this.reply = do_reply;
this.level = 1;
this.fd = EFile.this.fd;
}
long out_pos;
@Override
public void async() {
if ((flags & EFILE_COMPRESSED) != 0) {
this.result_ok = false;
this.posix_errno = Posix.EINVAL;
}
try {
switch (whence) {
case EFILE_SEEK_SET:
out_pos=off;
break;
case EFILE_SEEK_CUR:
long cur = fd.position();
out_pos= cur + off;
break;
case EFILE_SEEK_END:
cur = fd.size();
out_pos = cur - off;
break;
default:
this.result_ok = false;
this.posix_errno = Posix.EINVAL;
return;
}
fd.position(out_pos);
if (log.isLoggable(Level.FINE)) {
log.fine(""+EFile.this+" :: seek "+out_pos);
}
} catch (IOException e) {
this.result_ok = false;
this.posix_errno = IO.exception_to_posix_code(e);
}
this.result_ok = true;
}
@Override
public void ready() throws Pausable {
if (reply) {
if (result_ok) {
EFile.this.fd = fd;
ByteBuffer response = ByteBuffer.allocate(9);
response.put(FILE_RESP_NUMBER);
response.putLong(out_pos);
driver_output2(response, null);
} else {
reply_posix_error(posix_errno);
}
}
}
};
cq_enq(d);
} catch (OutOfMemoryError e) {
if (errp != null) {
errp[0] = Posix.ENOMEM;
}
return -1;
}
return 0;
}
private void reply_ev(byte response, long[] offsets, ByteBuffer[] res_ev) throws Pausable {
ByteBuffer tmp = ByteBuffer.allocate(1);
tmp.put(response);
driver_outputv(tmp, res_ev);
}
static FilenameFilter READDIR_FILTER = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return !".".equals(name) && !"..".equals(name);
}
};
@Override
protected void output(EHandle caller, ByteBuffer buf) throws Pausable {
FileAsync d;
final byte cmd = buf.get();
switch (cmd) {
case FILE_TRUNCATE: {
d = new FileAsync() {
{
level = 2;
fd = EFile.this.fd;
command = FILE_TRUNCATE;
}
@Override
public void async() {
again = false;
try {
long pos = fd.position();
fd.truncate(pos);
result_ok = true;
} catch (IOException e) {
result_ok = false;
posix_errno = IO.exception_to_posix_code(e);
}
}
@Override
public void ready() throws Pausable {
reply(EFile.this);
}
};
} break;
case FILE_FDATASYNC:
case FILE_FSYNC: {
d = new FileAsync() {
{
level = 2;
fd = EFile.this.fd;
command = cmd;
}
@Override
public void async() {
again = false;
try {
fd.force(true);
result_ok = true;
} catch (IOException e) {
result_ok = false;
posix_errno = IO.exception_to_posix_code(e);
}
}
@Override
public void ready() throws Pausable {
reply(EFile.this);
}
};
} break;
case FILE_LINK:
case FILE_SYMLINK: {
final String old_file = IO.strcpy(buf);
final String new_file = IO.strcpy(buf);
d = new FileAsync() {
@Override
public void async() {
Path existing = ERT.newFile(old_file).toPath();
Path link = new File(new_file).toPath();
try {
if (cmd == FILE_LINK) {
Files.createLink(link, existing);
} else if (cmd == FILE_SYMLINK) {
Files.createSymbolicLink(link, existing);
} else {
posix_errno = Posix.EUNKNOWN;
result_ok = false;
return;
}
result_ok = true;
} catch (IOException e) {
posix_errno = IO.exception_to_posix_code(e);
result_ok = false;
}
}
@Override
public void ready() throws Pausable {
reply(EFile.this);
}
};
} break;
case FILE_READLINK: {
d = new SimpleFileAsync(cmd, IO.strcpy(buf)) {
String outfile = null;
public void run() {
Path p = this.file.toPath();
if (!Files.exists(p, LinkOption.NOFOLLOW_LINKS)) {
result_ok = false;
posix_errno = Posix.ENOENT;
return;
}
if (!Files.isSymbolicLink(p)) {
result_ok = false;
posix_errno = Posix.EINVAL;
return;
}
try {
p = Files.readSymbolicLink(p);
outfile = p.toString();
result_ok = true;
} catch (IOException e) {
result_ok = false;
posix_errno = IO.exception_to_posix_code(e);
}
}
@Override
public void ready() throws Pausable {
if (!result_ok) {
super.ready();
} else {
ByteBuffer reply = null;
ByteBuffer data = null;
// prim_file interface from R14 on
reply = ByteBuffer.allocate(1);
data = ByteBuffer.allocate(outfile.length());
reply.put(FILE_RESP_FNAME);
IO.putstr(data, outfile, false);
driver_output2(reply, data);
}
}
};
} break;
case FILE_MKDIR: {
d = new SimpleFileAsync(cmd, IO.strcpy(buf)) {
public void run() {
result_ok = file.mkdir();
if (!result_ok) {
if (name.length() == 0) {
posix_errno = Posix.ENOENT;
} else if (file.exists()) {
posix_errno = Posix.EEXIST;
} else if (file.getParentFile() != null && !file.getParentFile().isDirectory()) {
posix_errno = Posix.ENOTDIR;
} else {
posix_errno = Posix.EUNKNOWN;
}
}
}
};
break;
}
case FILE_RMDIR: {
d = new SimpleFileAsync(cmd, IO.strcpy(buf)) {
public void run() {
result_ok = file.isDirectory() && file.delete();
if (!result_ok) {
if (!file.exists()) {
posix_errno = Posix.ENOENT;
} else if (Posix.isCWD(name, file)) {
posix_errno = Posix.EINVAL;
} else if (!file.isDirectory()) {
posix_errno = Posix.ENOTDIR;
} else if (file.exists()) {
posix_errno = Posix.EEXIST;
} else {
posix_errno = Posix.EUNKNOWN;
}
}
}
};
break;
}
case FILE_CHDIR: {
d = new SimpleFileAsync(cmd, IO.strcpy(buf)) {
public void run() {
result_ok = file.isDirectory();
if (!result_ok) {
if (!file.exists()) {
posix_errno = Posix.ENOENT;
} else if (!file.isDirectory()) {
posix_errno = Posix.ENOTDIR;
} else {
posix_errno = Posix.EUNKNOWN;
}
} else {
try {
System.setProperty("user.dir", file.getCanonicalPath());
} catch (IOException e) {
posix_errno = Posix.EUNKNOWN;
}
}
}
};
break;
}
case FILE_DELETE: {
d = new SimpleFileAsync(cmd, IO.strcpy(buf)) {
public void run() {
result_ok = file.isFile() && file.delete();
if (!result_ok) {
if (!file.exists()) {
posix_errno = Posix.ENOENT;
} else if (!file.canWrite()) {
posix_errno = Posix.EPERM;
} else if (file.isDirectory()) {
posix_errno = Posix.EEXIST;
} else {
posix_errno = Posix.EUNKNOWN;
}
}
}
};
break;
}
case FILE_PWD: {
d = new FileAsync() {
private String pwd;
{
this.command = FILE_PWD;
super.level = 2;
}
@Override
public void async() {
File pwd = Posix.getCWD();
if (pwd.exists() && pwd.isDirectory()) {
this.pwd = pwd.getAbsolutePath();
result_ok = true;
} else {
result_ok = false;
posix_errno = Posix.ENOENT;
}
again = false;
}
@Override
public void ready() throws Pausable {
if (!result_ok) {
reply_posix_error(posix_errno);
} else {
ByteBuffer reply = null;
ByteBuffer data = null;
if (isUnicodeDriverInterface()) {
// prim_file interface from R14 on
reply = ByteBuffer.allocate(1);
data = ByteBuffer.allocate(pwd.length());
reply.put(FILE_RESP_FNAME);
IO.putstr(data, pwd, false);
}
else {
// prim_file interface up to R13B
reply = ByteBuffer.allocate(1+pwd.length());
reply.put(FILE_RESP_OK);
IO.putstr(reply, pwd, false);
}
driver_output2(reply, data);
}
}
};
break;
}
case FILE_LSEEK: {
final long off = buf.getLong();
final int whence = buf.getInt();
async_lseek(null, true, off, whence);
return;
}
case FILE_OPEN: {
final int mode = buf.getInt();
final String file_name = IO.strcpy(buf);
d = new SimpleFileAsync(cmd, file_name) {
int res_fd = 1234;
public void run() {
boolean compressed = (mode & EFILE_COMPRESSED) > 0;
if (compressed && log.isLoggable(Level.FINE)) {
log.fine("EFile.open_compressed "+file_name);
}
boolean append = (mode & EFILE_MODE_APPEND) > 0;
if ((mode & ~(EFILE_MODE_APPEND | EFILE_MODE_READ_WRITE | EFILE_MODE_EXCL)) > 0) {
log.warning("ONLY APPEND AND READ_WRITE OPTIONS ARE IMPLEMENTED! mode="+mode);
throw new NotImplemented();
}
try {
if (compressed) {
if ((mode & EFILE_MODE_READ_WRITE) == EFILE_MODE_READ_WRITE && append) {
posix_errno = Posix.EINVAL;
return;
}
log.warning("COMPRESSED NOT IMPLEMENTED!");
throw new NotImplemented();
} else {
if ((mode & EFILE_MODE_EXCL) == EFILE_MODE_EXCL) {
file.createNewFile();
}
switch (mode & EFILE_MODE_READ_WRITE) {
case EFILE_MODE_READ: {
FileInputStream fo = new FileInputStream(file);
fd = fo.getChannel();
res_fd = getFDnumber(fo.getFD());
break;
}
case EFILE_MODE_WRITE: {
FileOutputStream fo = new FileOutputStream(file);
fd = fo.getChannel();
res_fd = getFDnumber(fo.getFD());
break;
}
case EFILE_MODE_READ_WRITE: {
RandomAccessFile rafff;
fd = (rafff=new RandomAccessFile(file,"rw")).getChannel();
res_fd = getFDnumber(rafff.getFD());
break;
}
default:
throw new NotImplemented();
}//switch
EFile.this.name = file;
result_ok = true;
}
} catch (FileNotFoundException fnfe) {
posix_errno = fileNotFound_to_posixErrno(file, mode);
} catch (IOException e) {
log.log(Level.WARNING, "failed to open file", e);
posix_errno = fileNotFound_to_posixErrno(file, mode);
} catch (IllegalAccessException e) {
log.log(Level.WARNING, "failed to open file", e);
posix_errno = fileNotFound_to_posixErrno(file, mode);
}
}
@Override
public void ready() throws Pausable {
if (result_ok) {
EFile.this.fd = fd;
reply_Uint(res_fd); /* TODO: fd */
} else {
reply_posix_error(posix_errno);
}
}
};
} break;
case FILE_WRITE_INFO: {
final int file_mode = buf.getInt();
final int file_uid = buf.getInt();
final int file_gid = buf.getInt();
final FileTime file_atime = FileTime.from( buf.getLong(), TimeUnit.SECONDS );
final FileTime file_mtime = FileTime.from( buf.getLong(), TimeUnit.SECONDS );
final FileTime file_ctime = FileTime.from( buf.getLong(), TimeUnit.SECONDS );
final String file_name = IO.strcpy(buf);
if (ClassPathResource.isResource(file_name)) {
reply_posix_error(Posix.EPERM);
return;
}
d = new SimpleFileAsync(cmd, file_name) {
@Override
protected void run() throws IOException {
if (!file.exists()) {
this.posix_errno = Posix.ENOENT;
this.result_ok = false;
return;
}
Path path = file.toPath();
Files.setLastModifiedTime(path, file_mtime);
Map<String,Object> atts = Files.readAttributes(path, "unix:mode,gid,uid", LinkOption.NOFOLLOW_LINKS);
if (att(atts, "mode", file_mode) != file_mode) {
Files.setAttribute(path, "unix:mode", new Integer(file_mode), LinkOption.NOFOLLOW_LINKS);
}
if (att(atts, "gid", file_gid) != file_gid) {
Files.setAttribute(path, "unix:gid", new Integer(file_gid), LinkOption.NOFOLLOW_LINKS);
}
if (att(atts, "uid", file_uid) != file_uid) {
Files.setAttribute(path, "unix:uid", new Integer(file_uid), LinkOption.NOFOLLOW_LINKS);
}
this.result_ok = true;
}
int att(Map<String,Object> atts, String name, int defaultValue) {
Number att = (Number)atts.get(name);
if (att == null) return defaultValue;
return att.intValue();
}
};
} break;
case FILE_FSTAT:
case FILE_LSTAT: {
final String file_name = IO.strcpy(buf);
final File file = ERT.newFile(file_name);
if (ClassPathResource.isResource(file_name)) {
ClassPathResource.fstat(this, file_name);
return;
}
d = new FileAsync() {
long file_size;
int file_type;
long file_access_time;
long file_modify_time;
long file_create_time;
int file_inode;
int file_gid;
int file_uid;
int file_access;
int file_mode;
int file_nlink;
int file_dev;
int file_rdev;
/** emulate fstat as close as possible */
@Override
public void async() {
if (!file.exists()) {
result_ok = false;
posix_errno = Posix.ENOENT;
return;
}
Path path = file.toPath();
PosixFileAttributes attrs;
try {
attrs = Files.readAttributes(path, PosixFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
} catch (IOException e) {
posix_errno = IO.exception_to_posix_code(e);
result_ok = false;
return;
}
file_size = attrs.size();
if (attrs.isDirectory()) {
file_type = FT_DIRECTORY;
} else if (attrs.isRegularFile()) {
file_type = FT_REGULAR;
} else if (attrs.isSymbolicLink()) {
file_type = FT_SYMLINK;
} else if (attrs.isOther()) {
file_type = FT_OTHER;
} else {
file_type = FT_DEVICE;
}
file_access_time = attrs.lastAccessTime().to(TimeUnit.SECONDS);
file_create_time = attrs.creationTime().to(TimeUnit.SECONDS);
file_modify_time = attrs.lastModifiedTime().to(TimeUnit.SECONDS);
file_access = 0;
if (Files.isReadable(path)) file_access |= FILE_ACCESS_READ;
if (Files.isWritable(path)) file_access |= FILE_ACCESS_WRITE;
Map<String,Object> unix_attrs = null;
try {
unix_attrs = Files.readAttributes(path, "unix:mode,ino,uid,gid,nlink,dev,rdev", LinkOption.NOFOLLOW_LINKS);
} catch (UnsupportedOperationException e) {
// ok //
} catch (IOException e) {
posix_errno = IO.exception_to_posix_code(e);
result_ok = false;
return;
}
if (unix_attrs != null && unix_attrs.containsKey("mode")) {
file_mode = att(unix_attrs, "mode", 0);
file_inode = att(unix_attrs, "ino", 0);
file_uid = att(unix_attrs, "uid", 0);
file_gid = att(unix_attrs, "gid", 0);
file_nlink = att(unix_attrs, "nlink", 1);
file_dev = att(unix_attrs, "dev", 0);
file_rdev = att(unix_attrs, "rdev", 0);
} else {
file_inode = file.hashCode();
file_nlink = 1;
file_mode = 0;
for (PosixFilePermission p : attrs.permissions()) {
switch (p) {
case OTHERS_READ:
file_mode |= 0000004;
break;
case OTHERS_WRITE:
file_mode |= 0000002;
break;
case OTHERS_EXECUTE:
file_mode |= 0000001;
break;
case GROUP_READ:
file_mode |= 0000040;
break;
case GROUP_WRITE:
file_mode |= 0000020;
break;
case GROUP_EXECUTE:
file_mode |= 0000010;
break;
case OWNER_READ:
file_mode |= 0000400;
break;
case OWNER_WRITE:
file_mode |= 0000200;
break;
case OWNER_EXECUTE:
file_mode |= 0000100;
break;
}
}
switch (file_type) {
case FT_DIRECTORY:
file_mode |= 0040000;
break;
case FT_REGULAR:
file_mode |= 0100000;
break;
case FT_SYMLINK:
file_mode |= 0120000;
break;
}
}
result_ok = true;
}
@Override
public void ready() throws Pausable {
if (!this.result_ok) {
reply_posix_error(posix_errno);
return;
}
final int RESULT_SIZE = (1 + (17 * 4));
ByteBuffer res = ByteBuffer.allocate(RESULT_SIZE);
res.order(ByteOrder.BIG_ENDIAN);
res.put(FILE_RESP_INFO);
res.putLong(file_size);
res.putInt(file_type);
res.putLong(file_access_time);
res.putLong(file_modify_time);
res.putLong(file_create_time);
res.putInt(file_mode);
res.putInt(file_nlink);
res.putInt(file_dev);
res.putInt(file_rdev);
res.putInt(file_inode);
res.putInt(file_uid);
res.putInt(file_gid);
res.putInt(file_access);
driver_output2(res, null);
}
int att(Map<String,Object> atts, String name, int defaultValue) {
Number att = (Number)atts.get(name);
if (att == null) return defaultValue;
return att.intValue();
}
};
break;
}
case FILE_READDIR: {
final String dir_name = IO.strcpy(buf);
if (dir_name.startsWith(RESOURCE_PREFIX)) {
ClassPathResource.listdir(this, dir_name.substring(RESOURCE_PREFIX.length()));
return;
}
//final File cwd = new File(System.getProperty("user.dir")).getAbsoluteFile();
final File dir = ERT.newFile(/*cwd, */dir_name);
d = new FileAsync() {
{
super.level = 2;
}
String[] files;
@Override
public void async() {
if (!dir.exists()) {
this.posix_errno = Posix.ENOENT;
this.result_ok = false;
return;
}
if (!dir.isDirectory()) {
this.posix_errno = Posix.ENOTDIR;
this.result_ok = false;
return;
}
try {
files = dir.list(READDIR_FILTER);
this.result_ok = true;
} catch (SecurityException e) {
this.posix_errno = Posix.EPERM;
this.result_ok = false;
return;
}
}
@Override
public void ready() throws Pausable {
if (!this.result_ok) {
reply_posix_error(posix_errno);
return;
}
reply_list_directory(files);
}
};
break;
}
case FILE_RENAME: {
final String from_name = IO.getstr(buf, true);
final File to_name = ERT.newFile(IO.getstr(buf, true));
if (log.isLoggable(Level.FINE))
log.fine(""+this+"rename "+from_name+" -> "+to_name);
d = new SimpleFileAsync(cmd, from_name) {
public void run() {
this.result_ok = file.renameTo(to_name);
if (!result_ok) {
if (!file.exists()) {
posix_errno = Posix.ENOENT;
} else if (to_name.exists()) {
posix_errno = Posix.EEXIST;
} else {
posix_errno = Posix.EUNKNOWN;
}
}
}
};
break;
}
case FILE_FADVISE: {
// fadvice() is not available from Java,
// so we simply ignore it and return success
reply_ok();
return;
}
case FILE_SETOPT: {
reply_ok();
return;
}
default:
log.warning("invalid file_output cmd:" + ((int) cmd) + " "
+ EBinary.make(buf));
driver_output_binary(new byte[]{ FILE_RESP_ERROR },
ByteBuffer.wrap( ( "unknown_cmd_"+((int)cmd)).getBytes() ));
return;
/** ignore everything else - let the caller hang */
// return;
}
if (d != null) {
cq_enq(d);
}
}
@Override
public void processExit(ERef monitor) throws Pausable {
// TODO Auto-generated method stub
}
@Override
protected void readyAsync(EAsync data) throws Pausable {
FileAsync d = (FileAsync) data;
if (try_again(d))
return;
// do whatever for this kind of async job
d.ready();
if (write_buffered != 0 && timer_state == TimerState.IDLE) {
timer_state = TimerState.WRITE;
driver_set_timer(write_delay);
}
cq_execute();
}
/**
* @param d
* @return
*/
private boolean try_again(FileAsync d) {
if (!d.again) {
return false;
}
d.deq_free_size();
if (timer_state != TimerState.IDLE) {
driver_cancel_timer();
}
timer_state = TimerState.AGAIN;
invoke = d;
driver_set_timer(0);
return true;
}
@Override
protected void readyInput(SelectableChannel ch) throws Pausable {
throw new InternalError("should not happen");
}
@Override
protected void readyOutput(SelectableChannel evt) throws Pausable {
throw new InternalError("should not happen");
}
@Override
protected void timeout() throws Pausable {
TimerState timer_state = this.timer_state;
this.timer_state = TimerState.IDLE;
switch (timer_state) {
case IDLE:
assert (false) : "timeout in idle state?";
return;
case AGAIN:
assert (invoke != null);
driver_async(invoke);
break;
case WRITE:
int r = flush_write(null);
assert (r == 0);
cq_execute();
}
}
/**
* @return
*/
private int flush_write(int[] errp) {
int result;
q_mtx.lock();
try {
if (this.write_buffered > 0) {
result = async_write(null, false, 0);
} else {
result = 0;
}
} finally {
q_mtx.unlock();
}
return result;
}
// // CQ OPERATIONS //
/**
* @param errp
* @param reply
* true if we should send a reply
* @param reply_size
* value to send in reply
* @return
*/
private int async_write(int[] errp, boolean reply, int reply_size) {
try {
FileAsync cmd = new WriteAsync(reply, reply_size);
cq_enq(cmd);
write_buffered = 0;
return 0;
} catch (OutOfMemoryError e) {
if (errp == null) {
throw e;
}
if (errp != null)
errp[0] = Posix.ENOMEM;
return -1;
}
}
private void cq_enq(FileAsync d) {
cq.add(d);
}
private FileAsync cq_deq() {
return cq.poll();
}
private void cq_execute() throws Pausable {
if (timer_state == TimerState.AGAIN)
return;
FileAsync d;
if ((d = cq_deq()) == null)
return;
d.again = false; /* (SYS_INFO.async_threads == 0); */
if (THREAD_SHORT_CIRCUIT >= level) {
d.async();
this.readyAsync(d);
} else {
driver_async(d);
}
}
//
void reply_buf(ByteBuffer buf) throws Pausable {
ByteBuffer header = ByteBuffer.allocate(1 + 4 + 4);
header.put(FILE_RESP_DATA);
header.putLong(buf.position());
driver_output2(header, buf);
}
void reply_eof() throws Pausable {
ByteBuffer header = ByteBuffer.allocate(1);
header.put(FILE_RESP_EOF);
driver_output2(header, null);
}
/**
* @throws Pausable
*
*/
public void reply_ok() throws Pausable {
ByteBuffer header = ByteBuffer.allocate(1);
header.put(FILE_RESP_OK);
driver_output2(header, null);
}
protected static int fileNotFound_to_posixErrno(File file, int mode) {
if (!file.exists() || !file.isFile())
return Posix.ENOENT;
else if ((mode & EFILE_MODE_READ) > 0 && !file.canRead())
return Posix.EPERM;
else if ((mode & EFILE_MODE_WRITE) > 0 && !file.canWrite())
return Posix.EPERM;
else
return Posix.EUNKNOWN;
}
@Override
public String toString() {
String pos;
try {
pos = (fd==null?"?":"0x"+Long.toHexString (fd.position()));
} catch (IOException e) {
pos = "?";
}
return "EFile[name=\""+name+"\";pos="+pos+"]";
}
void reply_list_directory(String[] files) throws Pausable {
for (int i = 0; i < files.length; i++) {
if (isLFNameDriverInterface()) {
// prim_file interface from R15 on
ByteBuffer reply = ByteBuffer.allocate(1);
reply.put(FILE_RESP_LFNAME);
ByteBuffer data = ByteBuffer.allocate(2+files[i].length());
data.limit(data.capacity());
data.position(0);
data.putShort((short)files[i].length());
IO.putstr(data, files[i], false);
driver_output2(reply, data);
} else if (isUnicodeDriverInterface()) {
// prim_file interface from R14 on
ByteBuffer reply = ByteBuffer.allocate(1);
reply.put(FILE_RESP_FNAME);
ByteBuffer data = ByteBuffer.allocate(files[i].length());
data.limit(data.capacity());
data.position(0);
IO.putstr(data, files[i], false);
driver_output2(reply, data);
}
else {
// prim_file interface up to R13B
ByteBuffer resbuf = ByteBuffer.allocate(files[i].length()+1);
resbuf.put(FILE_RESP_OK);
resbuf.limit(resbuf.capacity());
resbuf.position(1);
IO.putstr(resbuf, files[i], false);
driver_output2(resbuf, null);
}
}
if (isLFNameDriverInterface()) {
ByteBuffer resbuf = ByteBuffer.allocate(1);
resbuf.put(FILE_RESP_LFNAME);
driver_output2(resbuf, null);
} else {
ByteBuffer resbuf = ByteBuffer.allocate(1);
resbuf.put(isUnicodeDriverInterface() ? FILE_RESP_FNAME : FILE_RESP_OK);
driver_output2(resbuf, null);
}
}
/**
* Determine whether to use the new unicode driver interface
* from R14B01.
*
* @return <code>true</code>, if the new driver interface
* from R14B01 is to be used, <code>false</code> for the older
* driver interface up to R13
*
* @see http://www.erlang.org/doc/apps/stdlib/unicode_usage.html#id60205
*/
private static boolean isUnicodeDriverInterface() {
return ERT.runtime_info.unicodeDriverInterface;
}
private static boolean isLFNameDriverInterface() {
return ERT.runtime_info.unicodeDriverInterface;
}
}