X-Git-Url: https://git.distorted.org.uk/~mdw/tripe-android/blobdiff_plain/04a5abaece151705e9bd7026653f79938a7a2fbc..HEAD:/sys.scala?ds=sidebyside diff --git a/sys.scala b/sys.scala index 51ac170..43487b5 100644 --- 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); @@ -438,8 +443,11 @@ def stat(path: String): sys.FileInfo = stat(path.toCString); def lstat(path: String): sys.FileInfo = lstat(path.toCString); object FileInfo extends Enumeration { - /* A simple enumeration of things a file might be. */ - 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._; @@ -603,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); } @@ -637,19 +646,15 @@ object FileImplicits { def islnk_! : Boolean = statish(lstat _, _.ftype == LNK, false); def issock_! : Boolean = statish(stat _, _.ftype == SOCK, false); + /* Slightly more cooked file operations. */ def remove_!() { /* Delete a file, or directory, whatever it is. */ - while (true) { - try { unlink_!(); return; } - catch { - case SystemError(ENOENT, _) => return; - case SystemError(EISDIR, _) => ok; - } - try { rmdir_!(); return; } - catch { - case SystemError(ENOENT, _) => return; - case SystemError(ENOTDIR, _) => ok; - } + try { unlink_!(); return; } + catch { + case SystemError(ENOENT, _) => return; + case SystemError(EISDIR, _) => + try { rmdir_!(); return; } + catch { case SystemError(ENOENT, _) => return; } } } @@ -662,6 +667,12 @@ object FileImplicits { 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); @@ -674,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) } @@ -687,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(); } } } } @@ -768,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(); } @@ -791,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); @@ -805,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; @@ -824,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; @@ -854,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 ----------------------------------------------*/