keys.scala, etc.: Make merging public keys have a progress bar.
[tripe-android] / sys.scala
index 6931431..43487b5 100644 (file)
--- a/sys.scala
+++ b/sys.scala
@@ -28,16 +28,19 @@ package uk.org.distorted.tripe; package object sys {
 /*----- Imports -----------------------------------------------------------*/
 
 import scala.collection.convert.decorateAsJava._;
-import scala.collection.mutable.HashSet;
+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];
@@ -124,7 +127,7 @@ import StringImplicits._;
 /*----- Main code ---------------------------------------------------------*/
 
 /* Import the native code library. */
-System.loadLibrary("toy");
+System.loadLibrary("tripe");
 
 /* Native types.
  *
@@ -407,6 +410,8 @@ def unlink(path: String) { unlink(path.toCString); }
 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);
@@ -606,6 +611,7 @@ object FileImplicits {
     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); }
@@ -679,10 +685,8 @@ object FileImplicits {
     /* 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 InputStreamReader(open()));
-    def writer(): BufferedWriter =
-      new BufferedWriter(new OutputStreamWriter(openForOutput()));
+    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) }
@@ -692,13 +696,15 @@ object FileImplicits {
       val out = openForOutput();
       try { body(out) } finally { out.close(); }
     }
-    def withReader[T](body: BufferedReader => T): T = withInput { in =>
-      body(new BufferedReader(new InputStreamReader(in)))
-    };
-    def withWriter[T](body: BufferedWriter => T): T = withOutput { out =>
-      val w = new BufferedWriter(new OutputStreamWriter(out));
-      /* Do this the hard way, so that we flush the `BufferedWriter'. */
-      try { body(w) } finally { w.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(); }
     }
   }
 }
@@ -773,8 +779,9 @@ def runCommand(cmd: String*): (String, String) = {
   withCleaner { clean =>
 
     /* Create the child process and pick up the ends of its streams. */
-    val pb = new ProcessBuilder(cmd.asJava).redirectInput(devnull);
+    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(); }
 
@@ -796,7 +803,7 @@ def runCommand(cmd: String*): (String, String) = {
 
     /* Check the exit status. */
     val rc = kid.exitValue;
-    if (rc != 0) throw new SubprocessFailed(cmd, rc, berr.result);
+    if (rc) throw new SubprocessFailed(cmd, rc, berr.result);
 
     /* We're all done. */
     return (bout.result, berr.result);
@@ -810,15 +817,15 @@ private final val maxTriggers = 2;
 private var nTriggers = 0;
 private var triggers: List[Wrapper] = Nil;
 
-@native protected def makeTrigger(): Wrapper;
-@native protected def destroyTrigger(trig: Wrapper);
-@native protected def resetTrigger(trig: Wrapper);
+@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 == 0)
-      makeTrigger()
+    if (!nTriggers)
+      make_trigger()
     else {
       val trig = triggers.head;
       triggers = triggers.tail;
@@ -829,10 +836,10 @@ private def getTrigger(): Wrapper = {
 }
 
 private def putTrigger(trig: Wrapper) {
-  resetTrigger(trig);
+  reset_trigger(trig);
   triggerLock synchronized {
     if (nTriggers >= maxTriggers)
-      destroyTrigger(trig);
+      destroy_trigger(trig);
     else {
       triggers ::= trig;
       nTriggers += 1;
@@ -859,59 +866,69 @@ def interruptWithTrigger[T](body: Wrapper => T): T = {
   };
 }
 
-/*----- Connecting to a server --------------------------------------------*/
+/*----- Glue for the VPN server -------------------------------------------*/
 
-/* Primitive operations. */
-final val CF_CLOSERD = 1;
-final val CF_CLOSEWR = 2;
-final val CF_CLOSEMASK = CF_CLOSERD | CF_CLOSEWR;
-@native protected def connect(path: CString, trig: Wrapper): Wrapper;
-@native protected def send(conn: Wrapper, buf: CString,
-                          start: Int, len: Int, trig: Wrapper);
-@native protected def recv(conn: Wrapper, buf: CString,
-                          start: Int, len: Int, trig: Wrapper): Int;
-@native def closeconn(conn: Wrapper, how: Int);
-
-class Connection(path: String) extends Closeable {
-
-  /* The underlying primitive connection. */
-  private[this] val conn = interruptWithTrigger { trig =>
-    connect(path.toCString, trig);
-  };
-
-  /* Alternative constructors. */
-  def this(file: File) { this(file.getPath); }
+/* The lock class.  This is only a class because they're much easier to find
+ * than loose objects through JNI.
+ */
+private class ServerLock;
 
-  /* Cleanup.*/
-  override def close() { closeconn(conn, CF_CLOSEMASK); }
-  override protected def finalize() { super.finalize(); close(); }
+/* Exceptions. */
+class NameResolutionException(msg: String) extends Exception(msg);
+class InitializationException(msg: String) extends Exception(msg);
 
-  class Input private[Connection] extends InputStream {
-    /* An input stream which reads from the connection. */
+/* 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); }
+}
 
-    override def read(): Int = {
-      val buf = new Array[Byte](1);
-      val n = read(buf, 0, 1);
-      if (n < 0) -1 else buf(0)&0xff;
-    }
-    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(conn, buf, start, len, trig); };
-    override def close() { closeconn(conn, CF_CLOSERD); }
+/* 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 input = new Input;
-
-  class Output private[Connection] extends OutputStream {
-    /* An output stream which writes to the connection. */
+  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() { }
+}
 
-    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(conn, buf, start, len, trig); } }
-    override def close() { closeconn(conn, CF_CLOSEWR); }
-  }
-  lazy val output = new Output;
+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 ----------------------------------------------*/