/**
* 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;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import kilim.Pausable;
import kilim.Task;
import erjang.EAtom;
import erjang.EBinary;
import erjang.ECons;
import erjang.EHandle;
import erjang.EObject;
import erjang.EPID;
import erjang.ERT;
import erjang.ERef;
import erjang.ESeq;
import erjang.EString;
import erjang.ETuple2;
import erjang.ErlangError;
import erjang.NotImplemented;
import erjang.driver.EDriverTask.Mode;
import erjang.m.erlang.ErlPort;
/**
*
*/
public class ExecDriverInstance extends EDriverInstance {
private ETuple2 name;
private Process process;
private DataOutputStream out;
private DataInputStream in;
private DataInputStream err;
protected Thread stdin_thread;
protected Thread stderr_thread;
private boolean is_closing;
/**
* @param name
*/
public ExecDriverInstance(ETuple2 name) {
super(new ExecDriver(name));
this.name = name;
}
@Override
public void setup() {
HashMap<String, String> env = task.env;
String[] cmd = task.cmd;
String cwd = task.cwd;
String[] envp = new String[env.size()];
int pos = 0;
for (Map.Entry<String, String> e : env.entrySet()) {
envp[pos++] = e.getKey() + "=" + e.getValue();
}
try {
this.process = Runtime.getRuntime().exec(cmd, envp, ERT.newFile(cwd));
} catch (IOException e1) {
throw new ErlangError(e1);
}
List<String> al = new ArrayList<String>();
Collections.addAll(al, cmd);
// System.err.println("EXEC " + al);
this.out = new DataOutputStream(this.process.getOutputStream());
this.in = new DataInputStream(this.process.getInputStream());
this.err = new DataInputStream(this.process.getErrorStream());
if (!task.is_out_only) {
start_input_reader(in, true);
// also read stderr
if (task.stderr_to_stdout) {
start_input_reader(err, false);
}
}
}
synchronized void do_close() {
//System.err.println("closing: "+name);
this.is_closing = true;
Thread th = stderr_thread;
if (th != null) {
th.interrupt();
}
th = stdin_thread;
if (th != null) {
th.interrupt();
}
Process p = this.process;
if (p != null) {
p.destroy();
}
}
synchronized boolean is_closing() {
return this.is_closing;
}
private void start_input_reader(final DataInputStream stream,
final boolean is_stdin) {
new Thread() {
{ setDaemon(true); start(); }
public void run() {
try {
run0();
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
// this should not happen //
e.printStackTrace();
}
}
public void run0() {
if (is_stdin) {
stdin_thread = Thread.currentThread();
try {
boolean cont = false;
do {
try {
cont = do_read();
} catch (InterruptedIOException e) {
cont = !is_closing();
} catch (IOException e) {
cont = false;
}
} while (cont);
} finally {
stdin_thread = null;
while(stderr_thread != null) {
Thread t = stderr_thread;
if (t != null && !t.isAlive())
break;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (task.send_eof) {
task.eof_from_driver_b();
}
if (task.send_exit_status) {
int code;
while (true) {
try {
code = process.waitFor();
break;
} catch (InterruptedException e) {
continue;
}
}
task.exit_status_from_driver_b(code);
}
}
} else {
stderr_thread = Thread.currentThread();
try {
boolean cont = false;
do {
try {
cont = do_read();
} catch (InterruptedIOException e) {
cont = !is_closing();
} catch (IOException e) {
cont = false;
}
} while (cont);
} finally {
stderr_thread = null;
}
}
}
private boolean do_read() throws IOException {
byte[] data;
int nbytes;
if (task.mode == Mode.STREAM) {
data = new byte[Math.max(stream.available(), 512)];
try {
nbytes = stream.read(data);
} catch (IOException e) {
nbytes = 0;
}
} else if (task.mode == Mode.PACKET) {
switch (task.packet) {
case 1:
nbytes = stream.read();
break;
case 2:
nbytes = stream.readUnsignedShort();
break;
case 4:
nbytes = stream.readInt() & 0x7fffffff;
break;
default:
throw new InternalError();
}
if (nbytes <= 0) {
return false;
}
data = new byte[nbytes];
try {
stream.readFully(data);
} catch (IOException e) {
nbytes = 0;
}
} else {
throw new NotImplemented("mode=" + task.mode);
}
if (nbytes <= 0) {
return false;
}
if (task.send_binary_data) {
task.output_from_driver_b(new EBinary(data, 0, nbytes));
} else {
task.output_from_driver_b(EString.make(data, 0, nbytes));
}
return true;
}
};
}
/*
* (non-Javadoc)
*
* @see erjang.driver.EDriverInstance#call(int, erjang.EObject)
*/
@Override
protected EObject call(EPID pid, int command, EObject data) throws Pausable {
throw ERT.badarg(ERT.box(command), data);
}
/*
* (non-Javadoc)
*
* @see erjang.driver.EDriverInstance#flush()
*/
@Override
protected void flush() throws Pausable {
// TODO Auto-generated method stub
}
@Override
protected void stop(EObject reason) throws Pausable {
this.do_close();
}
/*
* (non-Javadoc)
*
* @see erjang.driver.EDriverInstance#output(java.nio.ByteBuffer)
*/
@Override
protected void output(EHandle caller, ByteBuffer buf) throws IOException,
Pausable {
byte[] data = buf.array();
int data_off = buf.position() + buf.arrayOffset();
int data_len = buf.remaining();
//System.err.println("out: " + data_len + "; "
// + EString.make(data, data_off, data_len));
switch (task.mode) {
case STREAM:
this.out.write(data, data_off, data_len);
this.out.flush();
return;
case PACKET:
switch (task.packet) {
case 1:
for (int off = 0; off < data_len; off += 256) {
int rest = Math.min(256, data_len - off);
out.writeByte(rest);
out.write(data, data_off + off, rest);
}
this.out.flush();
return;
case 2:
for (int off = 0; off < data_len; off += (1 << 16)) {
int rest = Math.min((1 << 16), data_len - off);
out.writeShort(rest);
out.write(data, data_off + off, rest);
}
this.out.flush();
return;
case 4:
out.writeInt(data_len);
out.write(data, data_off, data_len);
this.out.flush();
return;
default:
throw new Error("should not happen");
}
case LINE:
throw new NotImplemented();
}
this.out.flush();
}
/*
* (non-Javadoc)
*
* @see erjang.driver.EDriverInstance#processExit(erjang.ERef)
*/
@Override
public void processExit(ERef monitor) throws Pausable {
// TODO Auto-generated method stub
}
/*
* (non-Javadoc)
*
* @see erjang.driver.EDriverInstance#readyAsync(erjang.driver.EAsync)
*/
@Override
protected void readyAsync(EAsync data) throws Pausable {
// TODO Auto-generated method stub
}
/*
* (non-Javadoc)
*
* @see
* erjang.driver.EDriverInstance#readyInput(java.nio.channels.SelectableChannel
* )
*/
@Override
protected void readyInput(SelectableChannel ch) throws Pausable {
// TODO Auto-generated method stub
}
/*
* (non-Javadoc)
*
* @see
* erjang.driver.EDriverInstance#readyOutput(java.nio.channels.SelectableChannel
* )
*/
@Override
protected void readyOutput(SelectableChannel evt) throws Pausable {
// TODO Auto-generated method stub
}
/*
* (non-Javadoc)
*
* @see erjang.driver.EDriverInstance#timeout()
*/
@Override
protected void timeout() throws Pausable {
// TODO Auto-generated method stub
}
}