/*----- Imports -----------------------------------------------------------*/
-import scala.collection.mutable.HashSet;
-
-import java.io.{Closeable, File};
+import scala.collection.convert.decorateAsJava._;
+import scala.collection.mutable.{HashMap, HashSet};
+
+import java.io.{BufferedReader, BufferedWriter, Closeable, File,
+ FileDescriptor, FileInputStream, FileOutputStream,
+ FileReader, FileWriter,
+ InputStream, InputStreamReader,
+ OutputStream, OutputStreamWriter};
import java.nio.{ByteBuffer, CharBuffer};
import java.nio.charset.Charset;
import java.util.Date;
+import Implicits.truish;
+
/*----- Some magic for C strings ------------------------------------------*/
type CString = Array[Byte];
var sz: Int = (s.length*enc.averageBytesPerChar + 1).toInt;
var out = ByteBuffer.allocate(sz);
- while (true) {
+ loop[CString] { exit =>
/* If there's still stuff to encode, then encode it. Otherwise,
* there must be some dregs left in the encoder, so flush them out.
*/
val v = new Array[Byte](n + 1);
out.array.copyToArray(v, 0, n);
v(n) = 0;
- return v;
+ exit(v);
}
}
-
- /* Placate the type checker. */
- unreachable("unreachable");
}
}
/*----- Main code ---------------------------------------------------------*/
/* Import the native code library. */
-System.loadLibrary("toy");
+System.loadLibrary("tripe");
-/* Exception indicating that a wrapped native object has been clobbered. */
-class NativeObjectTypeException(msg: String) extends RuntimeException(msg);
+/* Native types.
+ *
+ * See `jni.c'. There's no good way to hand a C pointer into Java, so we
+ * just copy whole structures into Java byte arrays and hope. Well, also we
+ * tag them so we can detect mixups.
+ */
type Wrapper = Array[Byte];
+class NativeObjectTypeException(msg: String) extends RuntimeException(msg);
/*----- Error codes -------------------------------------------------------*/
+/* Machinery for collecting error information from C. */
protected case class ErrorEntry(val tag: String, val err: Int);
@native protected def errtab: Array[ErrorEntry];
@native protected def strerror(err: Int): CString;
object Errno extends Enumeration {
- private val tagmap = {
+ /* System errors.
+ *
+ * There are two slight difficulties here.
+ *
+ * * Not all target systems have the same errors. C has a preprocessor
+ * to deal with this, but we don't; so instead we'll define all of the
+ * errors we'll ever need, but maybe with bogus values.
+ *
+ * * Some systems reuse code numbers for two different error names, e.g.,
+ * both `EAGAIN' and `EWOULDBLOCK' are the same on Linux -- but not
+ * necessarily on other systems. Scala's `Enumeration' machinery
+ * doesn't like sharing `id' numbers between values.
+ *
+ * We augment the value type with an additional `code' value which is the
+ * actual system error code; we arbitrarily pick one error symbol with a
+ * given code to be `canonical', i.e., it has E.id == E.code; the others
+ * have synthetic `id' values. And symbols which don't correspond to any
+ * error on the target system have synthetic `id' /and/ `code', so that
+ * they can still be spoken about, but won't match any real error.
+ */
+
+ private val tagmap = { // map names to numbers based on what C reports
val b = Map.newBuilder[String, Int];
for (ErrorEntry(tag, err) <- errtab) b += tag -> err;
b.result
}
- private var wrong = -255;
- private val seen = HashSet[Int]();
- class ErrnoVal private[Errno](tag: String, val code: Int, id: Int)
- extends Val(id, tag) {
+ private val seen = HashSet[Int](); // which error codes have been taken
+
+ private var wrong = -256; // next synthetic code
+ private def nextwrong: Int = { val w = wrong; wrong -= 1; w }
+
+ class Val private[Errno](tag: String, val code: Int, id: Int)
+ extends super.Val(id, tag) {
+ /* Our augmented error type. */
+
def message: String = strerror(code).toJString;
}
+ private class UnknownError(code: Int)
+ extends Val("<unknown>", code, code);
+
+ private def err(tag: String, code: Int): Val = {
+ /* Construct an error symbol given its tag string and a code number. */
- private def err(tag: String, code: Int): ErrnoVal = {
- if (seen contains code) { wrong -= 1; new ErrnoVal(tag, code, wrong) }
- else { seen += code; new ErrnoVal(tag, code, code) }
+ if (code < 0) new Val(tag, code, code)
+ else if (seen contains code) new Val(tag, code, nextwrong)
+ else { seen += code; new Val(tag, code, code) }
}
- private def err(tag: String): ErrnoVal = err(tag, tagmap(tag));
+ private def err(tag: String): Val =
+ err(tag, tagmap.getOrElse(tag, nextwrong));
- val OK = err("OK", 0);
+ def byid(id: Int): Value = {
+ if (seen contains id) apply(id)
+ else new UnknownError(id)
+ }
+
+ val OK = err("OK", 0); // `errno' zero is a real thing
/*
;;; The errno name table is very boring to type. To make life less
val EHWPOISON = err("EHWPOISON");
/***end***/
}
-import Errno.{Value => _, _};
+import Errno.{Val => Errno, EEXIST, EISDIR, ENOENT, ENOTDIR};
object SystemError {
- def apply(err: Errno.Value, what: String): SystemError =
+ /* Pattern matching for `SystemError', below. */
+
+ def apply(err: Errno, what: String): SystemError =
new SystemError(err, what);
- def unapply(e: Exception): Option[(Errno.Value, String)] = e match {
+ def unapply(e: Exception): Option[(Errno, String)] = e match {
case e: SystemError => Some((e.err, e.what))
case _ => None
}
}
+class SystemError (val err: Errno, val what: String) extends Exception {
+ /* An error from a syscall or similar, usually from native code. */
-class SystemError private[this](val err: Errno.ErrnoVal, val what: String)
- extends Exception {
- def this(err: Errno.Value, what: String)
- { this(err.asInstanceOf[Errno.ErrnoVal], what); }
+ /* A constructor which takes an error number, for easier access from C. */
private def this(err: Int, what: CString)
- { this(Errno(err), what.toJString); }
+ { this(Errno.byid(err).asInstanceOf[Errno], what.toJString); }
+
override def getMessage(): String = s"$what: ${err.message}";
}
def rmdir(path: String) { rmdir(path.toCString); }
@native protected def mkdir(path: CString, mode: Int);
def mkdir(path: String, mode: Int) { mkdir(path.toCString, mode); }
+@native protected def chmod(path: CString, mode: Int);
+def chmod(path: String, mode: Int) { chmod(path.toCString, mode); }
@native protected def mkfile(path: CString, mode: Int);
def mkfile(path: String, mode: Int) { mkfile(path.toCString, mode); }
@native protected def rename(from: CString, to: CString);
def rename(from: String, to: String)
{ rename(from.toCString, to.toCString); }
+@native def fdint(fd: FileDescriptor): Int;
+@native def newfd(fd: Int): FileDescriptor;
+@native def isatty(fd: FileDescriptor): Boolean;
+
/*----- File status information -------------------------------------------*/
/* These are the traditional values, but the C code carefully arranges to
* return them regardless of what your kernel actually thinks.
*/
-val S_IFMT = 0xf000;
-val S_IFIFO = 0x1000;
-val S_IFCHR = 0x2000;
-val S_IFDIR = 0x4000;
-val S_IFBLK = 0x6000;
-val S_IFREG = 0x8000;
-val S_IFLNK = 0xa000;
-val S_IFSOCK = 0xc000;
-
+final val S_IFMT = 0xf000;
+final val S_IFIFO = 0x1000;
+final val S_IFCHR = 0x2000;
+final val S_IFDIR = 0x4000;
+final val S_IFBLK = 0x6000;
+final val S_IFREG = 0x8000;
+final val S_IFLNK = 0xa000;
+final val S_IFSOCK = 0xc000;
+
+/* Primitive read-the-file-status calls. */
@native protected def stat(path: CString): sys.FileInfo;
def stat(path: String): sys.FileInfo = stat(path.toCString);
@native protected def lstat(path: CString): sys.FileInfo;
def lstat(path: String): sys.FileInfo = lstat(path.toCString);
object FileInfo extends Enumeration {
- val FIFO, CHR, DIR, BLK, REG, LNK, SOCK, UNK = Value;
+ /* A simple enumeration of things a file might be.
+ *
+ * `HDLNK' is a hard link, used in `tar' files.
+ */
+ val FIFO, CHR, DIR, BLK, REG, LNK, SOCK, HDLNK, UNK = Value;
type Type = Value;
}
import FileInfo._;
-
class FileInfo private[this](val devMajor: Int, val devMinor: Int,
val ino: Long, val mode: Int, val nlink: Int,
val uid: Int, val gid: Int,
val blksize: Int, val blocks: Long,
val atime: Date, val mtime: Date,
val ctime: Date) {
+ /* Information about a file. This is constructed directly from native
+ * code.
+ */
+
private def this(devMajor: Int, devMinor: Int, ino: Long,
mode: Int, nlink: Int, uid: Int, gid: Int,
rdevMinor: Int, rdevMajor: Int,
size: Long, blksize: Int, blocks: Long,
atime: Long, mtime: Long, ctime: Long) {
+ /* Lightly cook the values from the underlying `struct stat'. */
+
this(devMajor, devMinor, ino, mode, nlink, uid, gid,
rdevMajor, rdevMinor, size, blksize, blocks,
new Date(atime), new Date(mtime), new Date(ctime));
}
+ /* Return the file permissions only. */
def perms: Int = mode&0xfff;
+ /* Return the filetype, as a `FileInfo.Type'. */
def ftype: Type = (mode&S_IFMT) match {
case S_IFIFO => FIFO
case S_IFCHR => CHR
}
private[this] def mustBeDevice() {
+ /* Insist that you only ask for `rdev' fields on actual device nodes. */
ftype match {
- case CHR | BLK => ();
+ case CHR | BLK => ok;
case _ => throw new IllegalArgumentException("Object is not a device");
}
}
+
+ /* Query the device-node numbers. */
def rdevMajor: Int = { mustBeDevice(); _rdevMajor }
def rdevMinor: Int = { mustBeDevice(); _rdevMinor }
}
/*----- Listing directories -----------------------------------------------*/
+/* Primitive operations. */
@native protected def opendir(path: CString): Wrapper;
@native protected def readdir(path: CString, dir: Wrapper): CString;
@native protected def closedir(path: CString, dir: Wrapper);
protected abstract class BaseDirIterator[T](cpath: CString)
extends LookaheadIterator[T] with Closeable {
+ /* The underlying machinery for directory iterators.
+ *
+ * Subclasses must define `mangle' to convert raw filenames into a T.
+ * We keep track of the path C-string, because we need to keep passing that
+ * back to C for inclusion in error messages. Recording higher-level
+ * things is left for subclasses.
+ */
+
+ /* Constructors from more convenient types. */
def this(path: String) { this(path.toCString); }
def this(dir: File) { this(dir.getPath); }
+
+ /* Cleaning up after ourselves. */
override def close() { closedir(cpath, dir); }
override protected def finalize() { super.finalize(); close(); }
- private[this] val dir = opendir(cpath);
+
+ /* Subclass responsibility. */
protected def mangle(file: String): T;
+
+ /* Main machinery. */
+ private[this] val dir = opendir(cpath);
override protected def fetch(): Option[T] = readdir(cpath, dir) match {
case null => None
case f => f.toJString match {
}
class DirIterator(val path: String) extends BaseDirIterator[String](path) {
+ /* Iterator over the basenames of files in a directory. */
+
def this(dir: File) { this(dir.getPath); }
+
override protected def mangle(file: String): String = file;
}
class DirFilesIterator private[this](val dir: File, cpath: CString)
extends BaseDirIterator[File](cpath) {
+ /* Iterator over full `File' objects in a directory. */
+
def this(dir: File) { this(dir, dir.getPath.toCString); }
def this(path: String) { this(new File(path), path.toCString); }
+
override protected def mangle(file: String): File = new File(dir, file);
}
/*----- File locking ------------------------------------------------------*/
-val LKF_EXCL = 1;
-val LKF_WAIT = 2;
-@native protected def lock(path: CString, flags: Int): Wrapper;
+/* Primitive operations. The low `mode' bits are for the lock file if we
+ * have to create it.
+ */
+final val LKF_EXCL = 0x1000;
+final val LKF_WAIT = 0x2000;
+@native protected def lock(path: CString, mode: Int): Wrapper;
@native protected def unlock(lock: Wrapper);
class FileLock(path: String, flags: Int) extends Closeable {
+ /* A class which represents a held lock on a file. */
+
+ /* Constructors. The default is to take an exclusive lock or fail
+ * immediately.
+ */
def this(file: File, flags: Int) { this(file.getPath, flags); }
- def this(path: String) { this(path, LKF_EXCL); }
- def this(file: File) { this(file.getPath, LKF_EXCL); }
+ def this(path: String) { this(path, LKF_EXCL | 0x1b6); }
+ def this(file: File) { this(file.getPath, LKF_EXCL | 0x1b6); }
+
+ /* The low-level lock object, actually a file descriptor. */
private[this] val lk = lock(path.toCString, flags);
+
+ /* Making sure things get cleaned up. */
override def close() { unlock(lk); }
override protected def finalize() { super.finalize(); close(); }
}
object FileImplicits {
implicit class FileOps(file: File) {
+ /* Augment `File' with operations which throw informative (if low-level
+ * and system-specific) exceptions rather than returning unhelpful
+ * win/lose booleans. These have names ending with `_!' because they
+ * might explode.
+ *
+ * And some other useful methods.
+ */
+ /* Constructing names of files in a directory. Honestly, I'm surprised
+ * there isn't a method for this already.
+ */
+ def /(sub: String): File = new File(file, sub);
+
+ /* Simple file operations. */
def unlink_!() { unlink(file.getPath); }
def rmdir_!() { rmdir(file.getPath); }
def mkdir_!(mode: Int) { mkdir(file.getPath, mode); }
def mkdir_!() { mkdir_!(0x1ff); }
+ def chmod_!(mode: Int) { chmod(file.getPath, mode); }
def mkfile_!(mode: Int) { mkfile(file.getPath, mode); }
def mkfile_!() { mkfile_!(0x1b6); }
+ def rename_!(to: File) { rename(file.getPath, to.getPath); }
+ /* Listing directories. */
def withFilesIterator[T](body: DirFilesIterator => T): T = {
val iter = new DirFilesIterator(file.getPath);
try { body(iter) } finally { iter.close(); }
}
+ def foreachFile(fn: File => Unit) { withFilesIterator(_.foreach(fn)) }
def files_! : Seq[File] = withFilesIterator { _.toSeq };
+ /* Low-level lFile information. */
def stat_! : FileInfo = stat(file.getPath);
def lstat_! : FileInfo = lstat(file.getPath);
+ /* Specific file-status queries. */
private[this] def statish[T](statfn: String => FileInfo,
ifexists: FileInfo => T,
ifmissing: => T): T =
def islnk_! : Boolean = statish(lstat _, _.ftype == LNK, false);
def issock_! : Boolean = statish(stat _, _.ftype == SOCK, false);
+ /* Slightly more cooked file operations. */
def remove_!() {
- while (true) {
- try { unlink_!(); return }
- catch {
- case SystemError(ENOENT, _) => return;
- case SystemError(EISDIR, _) => ();
- }
- try { rmdir_!(); return }
- catch {
- case SystemError(ENOENT, _) => return;
- case SystemError(ENOTDIR, _) => ();
- }
+ /* Delete a file, or directory, whatever it is. */
+ try { unlink_!(); return; }
+ catch {
+ case SystemError(ENOENT, _) => return;
+ case SystemError(EISDIR, _) =>
+ try { rmdir_!(); return; }
+ catch { case SystemError(ENOENT, _) => return; }
}
}
def rmTree() {
+ /* Delete a thing recursively. */
def walk(f: File) {
- if (f.isdir_!) f.withFilesIterator { _ foreach(walk _) };
+ if (f.isdir_!) f.foreachFile(walk _);
f.remove_!();
}
walk(file);
}
+ def mkdirNew_!() {
+ /* Make a directory if there's nothing there already. */
+ try { mkdir_!(); }
+ catch { case SystemError(EEXIST, _) => ok; }
+ }
+
+ /* File locking. */
+ def lock_!(flags: Int): FileLock = new FileLock(file.getPath, flags);
+ def lock_!(): FileLock = lock_!(LKF_EXCL | 0x1b6);
def withLock[T](flags: Int)(body: => T): T = {
- val lk = new FileLock(file.getPath, flags);
+ val lk = lock_!(flags);
try { body } finally { lk.close(); }
}
- def withLock[T](body: => T): T = withLock(LKF_EXCL) { body };
+ def withLock[T](body: => T): T = withLock(LKF_EXCL | 0x1b6) { body };
+
+ /* Opening files. Again, I'm surprised this isn't here already. */
+ def open(): FileInputStream = new FileInputStream(file);
+ def openForOutput(): FileOutputStream = new FileOutputStream(file);
+ def reader(): BufferedReader = new BufferedReader(new FileReader(file));
+ def writer(): BufferedWriter = new BufferedWriter(new FileWriter(file));
+ def withInput[T](body: FileInputStream => T): T = {
+ val in = open();
+ try { body(in) }
+ finally { in.close(); }
+ }
+ def withOutput[T](body: FileOutputStream => T): T = {
+ val out = openForOutput();
+ try { body(out) } finally { out.close(); }
+ }
+ def withReader[T](body: BufferedReader => T): T = {
+ val r = reader();
+ try { body(r) }
+ finally { r.close(); }
+ }
+ def withWriter[T](body: BufferedWriter => T): T = {
+ val w = writer();
+ try { body(w) }
+ finally { w.close(); }
+ }
}
}
import FileImplicits._;
val buf = new Array[Byte](6);
val b = new StringBuilder;
- while (true) {
+ loop[File] { exit =>
/* Keep going until we find a fresh one. */
/* Provide a prefix. Mostly this is to prevent the file starting with
/* Make the filename, and try to create the file. If we succeed, we
* win.
*/
- val f = new File(d, b.result); b.clear();
- try { f.mkfile_!(); return f; }
- catch { case SystemError(EEXIST, _) => (); }
+ val f = d/b.result; b.clear();
+ try { f.mkfile_!(); exit(f); }
+ catch { case SystemError(EEXIST, _) => ok; }
}
+}
+
+/*----- Running a command -------------------------------------------------*/
+
+private val devnull = new File("/dev/null");
- /* We shouldn't get here, but the type checker needs placating. */
- unreachable("unreachable");
+private def captureStream(in: InputStream, out: StringBuilder) {
+ /* Capture the INSTREAM's contents in a string. */
+
+ for ((buf, n) <- blocks(new InputStreamReader(in)))
+ out.appendAll(buf, 0, n);
}
-/*----- Connecting to a server --------------------------------------------*/
-
-val CF_CLOSERD = 1;
-val CF_CLOSEWR = 2;
-val CF_CLOSEMASK = CF_CLOSERD | CF_CLOSEWR;
-@native protected def connect(path: CString): Wrapper;
-@native protected def send(conn: Wrapper, buf: CString,
- start: Int, len: Int);
-@native protected def recv(conn: Wrapper, buf: CString,
- start: Int, len: Int): Int;
-@native def closeconn(conn: Wrapper, how: Int);
-
-class Connection(path: String) extends Closeable {
- def this(file: File) { this(file.getPath); }
- private[this] val conn = connect(path.toCString);
- override def close() { closeconn(conn, CF_CLOSEMASK); }
- override protected def finalize() { super.finalize(); close(); }
+class SubprocessFailed(val cmd: Seq[String], rc: Int, stderr: String)
+ extends Exception {
+ override def getMessage(): String =
+ s"process (${quoteTokens(cmd)}) failed (rc = $rc):\n" + stderr
+}
+
+def runCommand(cmd: String*): (String, String) = {
+ /* Run a command, returning its stdout and stderr. */
+
+ withCleaner { clean =>
+
+ /* Create the child process and pick up the ends of its streams. */
+ val pb = new ProcessBuilder(cmd.asJava);
+ val kid = pb.start(); clean { kid.destroy(); }
+ kid.getOutputStream.close();
+ val out = kid.getInputStream(); clean { out.close(); }
+ val err = kid.getErrorStream(); clean { err.close(); }
+
+ /* Capture the output in threads, so we don't block. Also, wait for the
+ * child to complete. Amazingly, messing with threads here isn't too
+ * much of a disaster.
+ */
+ val bout, berr = new StringBuilder;
+ val rdout = thread("capture process stdout", daemon = false) {
+ captureStream(out, bout);
+ }
+ val rderr = thread("capture process stderr", daemon = false) {
+ captureStream(err, berr);
+ }
+ val wait = thread("await process exit", daemon = false) {
+ kid.waitFor();
+ }
+ rdout.join(); rderr.join(); wait.join();
- class InputStream private[Connection] extends java.io.InputStream {
- override def read(): Int = {
- val buf = new Array[Byte](1);
- val n = read(buf, 0, 1);
- if (n < 0) -1 else buf(0)&0xff;
+ /* Check the exit status. */
+ val rc = kid.exitValue;
+ if (rc) throw new SubprocessFailed(cmd, rc, berr.result);
+
+ /* We're all done. */
+ return (bout.result, berr.result);
+ }
+}
+
+/*----- Interrupt triggers ------------------------------------------------*/
+
+private val triggerLock = new Object;
+private final val maxTriggers = 2;
+private var nTriggers = 0;
+private var triggers: List[Wrapper] = Nil;
+
+@native protected def make_trigger(): Wrapper;
+@native protected def destroy_trigger(trig: Wrapper);
+@native protected def reset_trigger(trig: Wrapper);
+@native protected def trigger(trig: Wrapper);
+
+private def getTrigger(): Wrapper = {
+ triggerLock synchronized {
+ if (!nTriggers)
+ make_trigger()
+ else {
+ val trig = triggers.head;
+ triggers = triggers.tail;
+ nTriggers -= 1;
+ trig
+ }
+ }
+}
+
+private def putTrigger(trig: Wrapper) {
+ reset_trigger(trig);
+ triggerLock synchronized {
+ if (nTriggers >= maxTriggers)
+ destroy_trigger(trig);
+ else {
+ triggers ::= trig;
+ nTriggers += 1;
}
- override def read(buf: Array[Byte]): Int =
- read(buf, 0, buf.length);
- override def read(buf: Array[Byte], start: Int, len: Int) =
- recv(conn, buf, start, len);
- override def close() { closeconn(conn, CF_CLOSERD); }
}
- lazy val input = new InputStream;
-
- class OutputStream private[Connection] extends java.io.OutputStream {
- override def write(b: Int) { write(Array[Byte](b.toByte), 0, 1); }
- override def write(buf: Array[Byte]) { write(buf, 0, buf.length); }
- override def write(buf: Array[Byte], start: Int, len: Int)
- { send(conn, buf, start, len); }
- override def close() { closeconn(conn, CF_CLOSEWR); }
+}
+
+private def withTrigger[T](body: Wrapper => T): T = {
+ val trig = getTrigger();
+ try { body(trig) }
+ finally { putTrigger(trig); }
+}
+
+def interruptWithTrigger[T](body: Wrapper => T): T = {
+ /* interruptWithTrigger { TRIG => BODY }
+ *
+ * Execute BODY and return its result. If the thread receives an
+ * interrupt, the trigger TRIG will be pulled. See `interruptably' for the
+ * full semantics.
+ */
+
+ withTrigger { trig =>
+ interruptably { body(trig) } onInterrupt { trigger(trig); }
+ };
+}
+
+/*----- Glue for the VPN server -------------------------------------------*/
+
+/* The lock class. This is only a class because they're much easier to find
+ * than loose objects through JNI.
+ */
+private class ServerLock;
+
+/* Exceptions. */
+class NameResolutionException(msg: String) extends Exception(msg);
+class InitializationException(msg: String) extends Exception(msg);
+
+/* Primitive operations. */
+@native protected def open_tun(): Int;
+@native protected def base_init();
+@native protected def setup_resolver();
+@native def load_keys(priv: CString, pub: CString, tag: CString);
+@native def unload_keys();
+@native def bind(host: CString, svc: CString);
+@native def unbind();
+@native def mark(seq: Int);
+@native def run();
+@native protected def send(buf: CString, start: Int, len: Int,
+ trig: Wrapper);
+@native protected def recv(buf: CString, start: Int, len: Int,
+ trig: Wrapper): Int;
+
+base_init();
+setup_resolver();
+
+/* Tunnel descriptor plumbing. */
+val pending = HashMap[String, Int]();
+
+def getTunnelFd(peer: CString): Int =
+ pending synchronized { pending(peer.toJString) };
+def storeTunnelFd(peer: String, fd: Int)
+ { pending synchronized { pending(peer) = fd; } }
+def withdrawTunnelFd(peer: String)
+ { pending synchronized { pending -= peer; } }
+def withTunnelFd[T](peer: String, fd: Int)(body: => T): T = {
+ storeTunnelFd(peer, fd);
+ try { body } finally { withdrawTunnelFd(peer); }
+}
+
+/* Server I/O. */
+lazy val serverInput: InputStream = new InputStream {
+ override def read(): Int = {
+ val buf = new Array[Byte](1);
+ val n = read(buf, 0, 1);
+ if (n < 0) -1 else buf(0)&0xff;
}
- lazy val output = new OutputStream;
+ override def read(buf: Array[Byte]): Int =
+ read(buf, 0, buf.length);
+ override def read(buf: Array[Byte], start: Int, len: Int) =
+ interruptWithTrigger { trig => recv(buf, start, len, trig); };
+ override def close() { }
+}
+
+lazy val serverOutput: OutputStream = new OutputStream {
+ override def write(b: Int) { write(Array[Byte](b.toByte), 0, 1); }
+ override def write(buf: Array[Byte]) { write(buf, 0, buf.length); }
+ override def write(buf: Array[Byte], start: Int, len: Int)
+ { interruptWithTrigger { trig => send(buf, start, len, trig); } }
+ override def close() { }
}
/*----- Crypto-library hacks ----------------------------------------------*/