chiark / gitweb /
8dab4c6daac5efa0e06d1e35b18d1618484ea51d
[tripe-android] / sys.scala
1 /* -*-scala-*-
2  *
3  * System-level hacking
4  *
5  * (c) 2018 Straylight/Edgeware
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of the Trivial IP Encryption (TrIPE) Android app.
11  *
12  * TrIPE is free software: you can redistribute it and/or modify it under
13  * the terms of the GNU General Public License as published by the Free
14  * Software Foundation; either version 3 of the License, or (at your
15  * option) any later version.
16  *
17  * TrIPE is distributed in the hope that it will be useful, but WITHOUT
18  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
20  * for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
24  */
25
26 package uk.org.distorted.tripe; package object sys {
27
28 /*----- Imports -----------------------------------------------------------*/
29
30 import scala.collection.convert.decorateAsJava._;
31 import scala.collection.mutable.{HashMap, HashSet};
32
33 import java.io.{BufferedReader, BufferedWriter, Closeable, File,
34                 FileDescriptor, FileInputStream, FileOutputStream,
35                 InputStream, InputStreamReader,
36                 OutputStream, OutputStreamWriter};
37 import java.nio.{ByteBuffer, CharBuffer};
38 import java.nio.charset.Charset;
39 import java.util.Date;
40
41 /*----- Some magic for C strings ------------------------------------------*/
42
43 type CString = Array[Byte];
44
45 /* We do this by hand, rather than relying on the JNI's built-in conversions,
46  * because we use the default encoding taken from the locale settings, rather
47  * than the ridiculous `modified UTF-8' which is (a) insensitive to the
48  * user's chosen locale and (b) not actually UTF-8 either.
49  */
50
51 class InvalidCStringException(msg: String) extends Exception(msg);
52
53 object StringImplicits {
54   implicit class ConvertJStringToCString(s: String) {
55     /* Magic to convert a string into a C string (null-terminated bytes). */
56
57     def toCString: CString = {
58       /* Convert the receiver to a C string. */
59
60       val enc = Charset.defaultCharset.newEncoder;
61       val in = CharBuffer.wrap(s);
62       var sz: Int = (s.length*enc.averageBytesPerChar + 1).toInt;
63       var out = ByteBuffer.allocate(sz);
64
65       loop[CString] { exit =>
66         /* If there's still stuff to encode, then encode it.  Otherwise,
67          * there must be some dregs left in the encoder, so flush them out.
68          */
69         val r = if (in.hasRemaining) enc.encode(in, out, true)
70                 else enc.flush(out);
71
72         /* Sift through the wreckage to figure out what to do. */
73         if (r.isError) r.throwException();
74         else if (r.isOverflow) {
75           /* No space in the buffer.  Make it bigger. */
76
77           sz *= 2;
78           val newout = ByteBuffer.allocate(sz);
79           out.flip(); newout.put(out);
80           out = newout;
81         } else if (r.isUnderflow) {
82           /* All done.  Check that there are no unexpected zero bytes -- so
83            * this will indeed be a valid C string -- and convert into a byte
84            * array that the C code will be able to pick apart.
85            */
86
87           out.flip(); val n = out.limit; val u = out.array;
88           if ({val z = u.indexOf(0); 0 <= z && z < n})
89             throw new InvalidCStringException("null byte in encoding");
90           val v = new Array[Byte](n + 1);
91           out.array.copyToArray(v, 0, n);
92           v(n) = 0;
93           exit(v);
94         }
95       }
96     }
97   }
98
99   implicit class ConvertCStringToJString(v: CString) {
100     /* Magic to convert a C string into a `proper' string. */
101
102     def toJString: String = {
103       /* Convert the receiver to a C string.
104        *
105        * We do this by hand, rather than relying on the JNI's built-in
106        * conversions, because we use the default encoding taken from the
107        * locale settings, rather than the ridiculous `modified UTF-8' which
108        * is (a) insensitive to the user's chosen locale and (b) not actually
109        * UTF-8 either.
110        */
111
112       val inlen = v.indexOf(0) match {
113         case -1 => v.length
114         case n => n
115       }
116       val dec = Charset.defaultCharset.newDecoder;
117       val in = ByteBuffer.wrap(v, 0, inlen);
118       dec.decode(in).toString
119     }
120   }
121 }
122 import StringImplicits._;
123
124 /*----- Main code ---------------------------------------------------------*/
125
126 /* Import the native code library. */
127 System.loadLibrary("tripe");
128
129 /* Native types.
130  *
131  * See `jni.c'.  There's no good way to hand a C pointer into Java, so we
132  * just copy whole structures into Java byte arrays and hope.  Well, also we
133  * tag them so we can detect mixups.
134  */
135 type Wrapper = Array[Byte];
136 class NativeObjectTypeException(msg: String) extends RuntimeException(msg);
137
138 /*----- Error codes -------------------------------------------------------*/
139
140 /* Machinery for collecting error information from C. */
141 protected case class ErrorEntry(val tag: String, val err: Int);
142 @native protected def errtab: Array[ErrorEntry];
143 @native protected def strerror(err: Int): CString;
144
145 object Errno extends Enumeration {
146   /* System errors.
147    *
148    * There are two slight difficulties here.
149    *
150    *   * Not all target systems have the same errors.  C has a preprocessor
151    *     to deal with this, but we don't; so instead we'll define all of the
152    *     errors we'll ever need, but maybe with bogus values.
153    *
154    *   * Some systems reuse code numbers for two different error names, e.g.,
155    *     both `EAGAIN' and `EWOULDBLOCK' are the same on Linux -- but not
156    *     necessarily on other systems.  Scala's `Enumeration' machinery
157    *     doesn't like sharing `id' numbers between values.
158    *
159    * We augment the value type with an additional `code' value which is the
160    * actual system error code; we arbitrarily pick one error symbol with a
161    * given code to be `canonical', i.e., it has E.id == E.code; the others
162    * have synthetic `id' values.  And symbols which don't correspond to any
163    * error on the target system have synthetic `id' /and/ `code', so that
164    * they can still be spoken about, but won't match any real error.
165    */
166
167   private val tagmap = { // map names to numbers based on what C reports
168     val b = Map.newBuilder[String, Int];
169     for (ErrorEntry(tag, err) <- errtab) b += tag -> err;
170     b.result
171   }
172
173   private val seen = HashSet[Int]();    // which error codes have been taken
174
175   private var wrong = -256;             // next synthetic code
176   private def nextwrong: Int = { val w = wrong; wrong -= 1; w }
177
178   class Val private[Errno](tag: String, val code: Int, id: Int)
179           extends super.Val(id, tag) {
180     /* Our augmented error type. */
181
182     def message: String = strerror(code).toJString;
183   }
184   private class UnknownError(code: Int)
185           extends Val("<unknown>", code, code);
186
187   private def err(tag: String, code: Int): Val = {
188     /* Construct an error symbol given its tag string and a code number. */
189
190     if (code < 0) new Val(tag, code, code)
191     else if (seen contains code) new Val(tag, code, nextwrong)
192     else { seen += code; new Val(tag, code, code) }
193   }
194   private def err(tag: String): Val =
195     err(tag, tagmap.getOrElse(tag, nextwrong));
196
197   def byid(id: Int): Value = {
198     if (seen contains id) apply(id)
199     else new UnknownError(id)
200   }
201
202   val OK = err("OK", 0);                // `errno' zero is a real thing
203
204   /*
205      ;;; The errno name table is very boring to type.  To make life less
206      ;;; awful, put the errno names in this list and evaluate the code to
207      ;;; get Emacs to regenerate it.
208
209      (let ((errors '(EPERM ENOENT ESRCH EINTR EIO ENXIO E2BIG ENOEXEC EBADF
210                      ECHILD EAGAIN ENOMEM EACCES EFAULT ENOTBLK EBUSY EEXIST
211                      EXDEV ENODEV ENOTDIR EISDIR EINVAL ENFILE EMFILE ENOTTY
212                      ETXTBSY EFBIG ENOSPC ESPIPE EROFS EMLINK EPIPE EDOM
213                      ERANGE
214
215                      EDEADLK ENAMETOOLONG ENOLCK ENOSYS ENOTEMPTY ELOOP
216                      EWOULDBLOCK ENOMSG EIDRM ECHRNG EL2NSYNC EL3HLT EL3RST
217                      ELNRNG EUNATCH ENOCSI EL2HLT EBADE EBADR EXFULL ENOANO
218                      EBADRQC EBADSLT EDEADLOCK EBFONT ENOSTR ENODATA ETIME
219                      ENOSR ENONET ENOPKG EREMOTE ENOLINK EADV ESRMNT ECOMM
220                      EPROTO EMULTIHOP EDOTDOT EBADMSG EOVERFLOW ENOTUNIQ
221                      EBADFD EREMCHG ELIBACC ELIBBAD ELIBSCN ELIBMAX ELIBEXEC
222                      EILSEQ ERESTART ESTRPIPE EUSERS ENOTSOCK EDESTADDRREQ
223                      EMSGSIZE EPROTOTYPE ENOPROTOOPT EPROTONOSUPPORT
224                      ESOCKTNOSUPPORT EOPNOTSUPP EPFNOSUPPORT EAFNOSUPPORT
225                      EADDRINUSE EADDRNOTAVAIL ENETDOWN ENETUNREACH ENETRESET
226                      ECONNABORTED ECONNRESET ENOBUFS EISCONN ENOTCONN
227                      ESHUTDOWN ETOOMANYREFS ETIMEDOUT ECONNREFUSED EHOSTDOWN
228                      EHOSTUNREACH EALREADY EINPROGRESS ESTALE EUCLEAN ENOTNAM
229                      ENAVAIL EISNAM EREMOTEIO EDQUOT ENOMEDIUM EMEDIUMTYPE
230                      ECANCELED ENOKEY EKEYEXPIRED EKEYREVOKED EKEYREJECTED
231                      EOWNERDEAD ENOTRECOVERABLE ERFKILL EHWPOISON)))
232        (save-excursion
233          (goto-char (point-min))
234          (search-forward (concat "***" "BEGIN errtab" "***"))
235          (beginning-of-line 2)
236          (delete-region (point)
237                         (progn
238                           (search-forward "***END***")
239                           (beginning-of-line)
240                           (point)))
241          (dolist (err errors)
242            (insert (format "  val %s = err(\"%s\");\n" err err)))))
243   */
244   /***BEGIN errtab***/
245   val EPERM = err("EPERM");
246   val ENOENT = err("ENOENT");
247   val ESRCH = err("ESRCH");
248   val EINTR = err("EINTR");
249   val EIO = err("EIO");
250   val ENXIO = err("ENXIO");
251   val E2BIG = err("E2BIG");
252   val ENOEXEC = err("ENOEXEC");
253   val EBADF = err("EBADF");
254   val ECHILD = err("ECHILD");
255   val EAGAIN = err("EAGAIN");
256   val ENOMEM = err("ENOMEM");
257   val EACCES = err("EACCES");
258   val EFAULT = err("EFAULT");
259   val ENOTBLK = err("ENOTBLK");
260   val EBUSY = err("EBUSY");
261   val EEXIST = err("EEXIST");
262   val EXDEV = err("EXDEV");
263   val ENODEV = err("ENODEV");
264   val ENOTDIR = err("ENOTDIR");
265   val EISDIR = err("EISDIR");
266   val EINVAL = err("EINVAL");
267   val ENFILE = err("ENFILE");
268   val EMFILE = err("EMFILE");
269   val ENOTTY = err("ENOTTY");
270   val ETXTBSY = err("ETXTBSY");
271   val EFBIG = err("EFBIG");
272   val ENOSPC = err("ENOSPC");
273   val ESPIPE = err("ESPIPE");
274   val EROFS = err("EROFS");
275   val EMLINK = err("EMLINK");
276   val EPIPE = err("EPIPE");
277   val EDOM = err("EDOM");
278   val ERANGE = err("ERANGE");
279   val EDEADLK = err("EDEADLK");
280   val ENAMETOOLONG = err("ENAMETOOLONG");
281   val ENOLCK = err("ENOLCK");
282   val ENOSYS = err("ENOSYS");
283   val ENOTEMPTY = err("ENOTEMPTY");
284   val ELOOP = err("ELOOP");
285   val EWOULDBLOCK = err("EWOULDBLOCK");
286   val ENOMSG = err("ENOMSG");
287   val EIDRM = err("EIDRM");
288   val ECHRNG = err("ECHRNG");
289   val EL2NSYNC = err("EL2NSYNC");
290   val EL3HLT = err("EL3HLT");
291   val EL3RST = err("EL3RST");
292   val ELNRNG = err("ELNRNG");
293   val EUNATCH = err("EUNATCH");
294   val ENOCSI = err("ENOCSI");
295   val EL2HLT = err("EL2HLT");
296   val EBADE = err("EBADE");
297   val EBADR = err("EBADR");
298   val EXFULL = err("EXFULL");
299   val ENOANO = err("ENOANO");
300   val EBADRQC = err("EBADRQC");
301   val EBADSLT = err("EBADSLT");
302   val EDEADLOCK = err("EDEADLOCK");
303   val EBFONT = err("EBFONT");
304   val ENOSTR = err("ENOSTR");
305   val ENODATA = err("ENODATA");
306   val ETIME = err("ETIME");
307   val ENOSR = err("ENOSR");
308   val ENONET = err("ENONET");
309   val ENOPKG = err("ENOPKG");
310   val EREMOTE = err("EREMOTE");
311   val ENOLINK = err("ENOLINK");
312   val EADV = err("EADV");
313   val ESRMNT = err("ESRMNT");
314   val ECOMM = err("ECOMM");
315   val EPROTO = err("EPROTO");
316   val EMULTIHOP = err("EMULTIHOP");
317   val EDOTDOT = err("EDOTDOT");
318   val EBADMSG = err("EBADMSG");
319   val EOVERFLOW = err("EOVERFLOW");
320   val ENOTUNIQ = err("ENOTUNIQ");
321   val EBADFD = err("EBADFD");
322   val EREMCHG = err("EREMCHG");
323   val ELIBACC = err("ELIBACC");
324   val ELIBBAD = err("ELIBBAD");
325   val ELIBSCN = err("ELIBSCN");
326   val ELIBMAX = err("ELIBMAX");
327   val ELIBEXEC = err("ELIBEXEC");
328   val EILSEQ = err("EILSEQ");
329   val ERESTART = err("ERESTART");
330   val ESTRPIPE = err("ESTRPIPE");
331   val EUSERS = err("EUSERS");
332   val ENOTSOCK = err("ENOTSOCK");
333   val EDESTADDRREQ = err("EDESTADDRREQ");
334   val EMSGSIZE = err("EMSGSIZE");
335   val EPROTOTYPE = err("EPROTOTYPE");
336   val ENOPROTOOPT = err("ENOPROTOOPT");
337   val EPROTONOSUPPORT = err("EPROTONOSUPPORT");
338   val ESOCKTNOSUPPORT = err("ESOCKTNOSUPPORT");
339   val EOPNOTSUPP = err("EOPNOTSUPP");
340   val EPFNOSUPPORT = err("EPFNOSUPPORT");
341   val EAFNOSUPPORT = err("EAFNOSUPPORT");
342   val EADDRINUSE = err("EADDRINUSE");
343   val EADDRNOTAVAIL = err("EADDRNOTAVAIL");
344   val ENETDOWN = err("ENETDOWN");
345   val ENETUNREACH = err("ENETUNREACH");
346   val ENETRESET = err("ENETRESET");
347   val ECONNABORTED = err("ECONNABORTED");
348   val ECONNRESET = err("ECONNRESET");
349   val ENOBUFS = err("ENOBUFS");
350   val EISCONN = err("EISCONN");
351   val ENOTCONN = err("ENOTCONN");
352   val ESHUTDOWN = err("ESHUTDOWN");
353   val ETOOMANYREFS = err("ETOOMANYREFS");
354   val ETIMEDOUT = err("ETIMEDOUT");
355   val ECONNREFUSED = err("ECONNREFUSED");
356   val EHOSTDOWN = err("EHOSTDOWN");
357   val EHOSTUNREACH = err("EHOSTUNREACH");
358   val EALREADY = err("EALREADY");
359   val EINPROGRESS = err("EINPROGRESS");
360   val ESTALE = err("ESTALE");
361   val EUCLEAN = err("EUCLEAN");
362   val ENOTNAM = err("ENOTNAM");
363   val ENAVAIL = err("ENAVAIL");
364   val EISNAM = err("EISNAM");
365   val EREMOTEIO = err("EREMOTEIO");
366   val EDQUOT = err("EDQUOT");
367   val ENOMEDIUM = err("ENOMEDIUM");
368   val EMEDIUMTYPE = err("EMEDIUMTYPE");
369   val ECANCELED = err("ECANCELED");
370   val ENOKEY = err("ENOKEY");
371   val EKEYEXPIRED = err("EKEYEXPIRED");
372   val EKEYREVOKED = err("EKEYREVOKED");
373   val EKEYREJECTED = err("EKEYREJECTED");
374   val EOWNERDEAD = err("EOWNERDEAD");
375   val ENOTRECOVERABLE = err("ENOTRECOVERABLE");
376   val ERFKILL = err("ERFKILL");
377   val EHWPOISON = err("EHWPOISON");
378   /***end***/
379 }
380 import Errno.{Val => Errno, EEXIST, EISDIR, ENOENT, ENOTDIR};
381
382 object SystemError {
383   /* Pattern matching for `SystemError', below. */
384
385   def apply(err: Errno, what: String): SystemError =
386     new SystemError(err, what);
387   def unapply(e: Exception): Option[(Errno, String)] = e match {
388     case e: SystemError => Some((e.err, e.what))
389     case _ => None
390   }
391 }
392 class SystemError (val err: Errno, val what: String) extends Exception {
393   /* An error from a syscall or similar, usually from native code. */
394
395   /* A constructor which takes an error number, for easier access from C. */
396   private def this(err: Int, what: CString)
397     { this(Errno.byid(err).asInstanceOf[Errno], what.toJString); }
398
399   override def getMessage(): String = s"$what: ${err.message}";
400 }
401
402 /*----- Basic file operations ---------------------------------------------*/
403
404 @native protected def unlink(path: CString);
405 def unlink(path: String) { unlink(path.toCString); }
406 @native protected def rmdir(path: CString);
407 def rmdir(path: String) { rmdir(path.toCString); }
408 @native protected def mkdir(path: CString, mode: Int);
409 def mkdir(path: String, mode: Int) { mkdir(path.toCString, mode); }
410 @native protected def mkfile(path: CString, mode: Int);
411 def mkfile(path: String, mode: Int) { mkfile(path.toCString, mode); }
412 @native protected def rename(from: CString, to: CString);
413 def rename(from: String, to: String)
414   { rename(from.toCString, to.toCString); }
415
416 @native def fdint(fd: FileDescriptor): Int;
417 @native def newfd(fd: Int): FileDescriptor;
418 @native def isatty(fd: FileDescriptor): Boolean;
419
420 /*----- File status information -------------------------------------------*/
421
422 /* These are the traditional values, but the C code carefully arranges to
423  * return them regardless of what your kernel actually thinks.
424  */
425 final val S_IFMT = 0xf000;
426 final val S_IFIFO = 0x1000;
427 final val S_IFCHR = 0x2000;
428 final val S_IFDIR = 0x4000;
429 final val S_IFBLK = 0x6000;
430 final val S_IFREG = 0x8000;
431 final val S_IFLNK = 0xa000;
432 final val S_IFSOCK = 0xc000;
433
434 /* Primitive read-the-file-status calls. */
435 @native protected def stat(path: CString): sys.FileInfo;
436 def stat(path: String): sys.FileInfo = stat(path.toCString);
437 @native protected def lstat(path: CString): sys.FileInfo;
438 def lstat(path: String): sys.FileInfo = lstat(path.toCString);
439
440 object FileInfo extends Enumeration {
441   /* A simple enumeration of things a file might be.
442    *
443    * `HDLNK' is a hard link, used in `tar' files.
444    */
445   val FIFO, CHR, DIR, BLK, REG, LNK, SOCK, HDLNK, UNK = Value;
446   type Type = Value;
447 }
448 import FileInfo._;
449
450 class FileInfo private[this](val devMajor: Int, val devMinor: Int,
451                              val ino: Long, val mode: Int, val nlink: Int,
452                              val uid: Int, val gid: Int,
453                              _rdevMinor: Int, _rdevMajor: Int,
454                              val size: Long,
455                              val blksize: Int, val blocks: Long,
456                              val atime: Date, val mtime: Date,
457                              val ctime: Date) {
458   /* Information about a file.  This is constructed directly from native
459    * code.
460    */
461
462   private def this(devMajor: Int, devMinor: Int, ino: Long,
463                    mode: Int, nlink: Int, uid: Int, gid: Int,
464                    rdevMinor: Int, rdevMajor: Int,
465                    size: Long, blksize: Int, blocks: Long,
466                    atime: Long, mtime: Long, ctime: Long) {
467     /* Lightly cook the values from the underlying `struct stat'. */
468
469     this(devMajor, devMinor, ino, mode, nlink, uid, gid,
470          rdevMajor, rdevMinor, size, blksize, blocks,
471          new Date(atime), new Date(mtime), new Date(ctime));
472   }
473
474   /* Return the file permissions only. */
475   def perms: Int = mode&0xfff;
476
477   /* Return the filetype, as a `FileInfo.Type'. */
478   def ftype: Type = (mode&S_IFMT) match {
479     case S_IFIFO => FIFO
480     case S_IFCHR => CHR
481     case S_IFDIR => DIR
482     case S_IFBLK => BLK
483     case S_IFREG => REG
484     case S_IFLNK => LNK
485     case S_IFSOCK => SOCK
486     case _ => UNK
487   }
488
489   private[this] def mustBeDevice() {
490     /* Insist that you only ask for `rdev' fields on actual device nodes. */
491     ftype match {
492       case CHR | BLK => ok;
493       case _ => throw new IllegalArgumentException("Object is not a device");
494     }
495   }
496
497   /* Query the device-node numbers. */
498   def rdevMajor: Int = { mustBeDevice(); _rdevMajor }
499   def rdevMinor: Int = { mustBeDevice(); _rdevMinor }
500 }
501
502 /*----- Listing directories -----------------------------------------------*/
503
504 /* Primitive operations. */
505 @native protected def opendir(path: CString): Wrapper;
506 @native protected def readdir(path: CString, dir: Wrapper): CString;
507 @native protected def closedir(path: CString, dir: Wrapper);
508
509 protected abstract class BaseDirIterator[T](cpath: CString)
510         extends LookaheadIterator[T] with Closeable {
511   /* The underlying machinery for directory iterators.
512    *
513    * Subclasses must define `mangle' to convert raw filenames into a T.
514    * We keep track of the path C-string, because we need to keep passing that
515    * back to C for inclusion in error messages.  Recording higher-level
516    * things is left for subclasses.
517    */
518
519   /* Constructors from more convenient types. */
520   def this(path: String) { this(path.toCString); }
521   def this(dir: File) { this(dir.getPath); }
522
523   /* Cleaning up after ourselves. */
524   override def close() { closedir(cpath, dir); }
525   override protected def finalize() { super.finalize(); close(); }
526
527   /* Subclass responsibility. */
528   protected def mangle(file: String): T;
529
530   /* Main machinery. */
531   private[this] val dir = opendir(cpath);
532   override protected def fetch(): Option[T] = readdir(cpath, dir) match {
533     case null => None
534     case f => f.toJString match {
535       case "." | ".." => fetch()
536       case jf => Some(mangle(jf))
537     }
538   }
539 }
540
541 class DirIterator(val path: String) extends BaseDirIterator[String](path) {
542   /* Iterator over the basenames of files in a directory. */
543
544   def this(dir: File) { this(dir.getPath); }
545
546   override protected def mangle(file: String): String = file;
547 }
548
549 class DirFilesIterator private[this](val dir: File, cpath: CString)
550         extends BaseDirIterator[File](cpath) {
551   /* Iterator over full `File' objects in a directory. */
552
553   def this(dir: File) { this(dir, dir.getPath.toCString); }
554   def this(path: String) { this(new File(path), path.toCString); }
555
556   override protected def mangle(file: String): File = new File(dir, file);
557 }
558
559 /*----- File locking ------------------------------------------------------*/
560
561 /* Primitive operations.  The low `mode' bits are for the lock file if we
562  * have to create it.
563  */
564 final val LKF_EXCL = 0x1000;
565 final val LKF_WAIT = 0x2000;
566 @native protected def lock(path: CString, mode: Int): Wrapper;
567 @native protected def unlock(lock: Wrapper);
568
569 class FileLock(path: String, flags: Int) extends Closeable {
570   /* A class which represents a held lock on a file. */
571
572   /* Constructors.  The default is to take an exclusive lock or fail
573    * immediately.
574    */
575   def this(file: File, flags: Int) { this(file.getPath, flags); }
576   def this(path: String) { this(path, LKF_EXCL | 0x1b6); }
577   def this(file: File) { this(file.getPath, LKF_EXCL | 0x1b6); }
578
579   /* The low-level lock object, actually a file descriptor. */
580   private[this] val lk = lock(path.toCString, flags);
581
582   /* Making sure things get cleaned up. */
583   override def close() { unlock(lk); }
584   override protected def finalize() { super.finalize(); close(); }
585 }
586
587 /*----- File implicits ----------------------------------------------------*/
588
589 object FileImplicits {
590   implicit class FileOps(file: File) {
591     /* Augment `File' with operations which throw informative (if low-level
592      * and system-specific) exceptions rather than returning unhelpful
593      * win/lose booleans.  These have names ending with `_!' because they
594      * might explode.
595      *
596      * And some other useful methods.
597      */
598
599     /* Constructing names of files in a directory.  Honestly, I'm surprised
600      * there isn't a method for this already.
601      */
602     def /(sub: String): File = new File(file, sub);
603
604     /* Simple file operations. */
605     def unlink_!() { unlink(file.getPath); }
606     def rmdir_!() { rmdir(file.getPath); }
607     def mkdir_!(mode: Int) { mkdir(file.getPath, mode); }
608     def mkdir_!() { mkdir_!(0x1ff); }
609     def mkfile_!(mode: Int) { mkfile(file.getPath, mode); }
610     def mkfile_!() { mkfile_!(0x1b6); }
611     def rename_!(to: File) { rename(file.getPath, to.getPath); }
612
613     /* Listing directories. */
614     def withFilesIterator[T](body: DirFilesIterator => T): T = {
615       val iter = new DirFilesIterator(file.getPath);
616       try { body(iter) } finally { iter.close(); }
617     }
618     def foreachFile(fn: File => Unit) { withFilesIterator(_.foreach(fn)) }
619     def files_! : Seq[File] = withFilesIterator { _.toSeq };
620
621     /* Low-level lFile information. */
622     def stat_! : FileInfo = stat(file.getPath);
623     def lstat_! : FileInfo = lstat(file.getPath);
624
625     /* Specific file-status queries. */
626     private[this] def statish[T](statfn: String => FileInfo,
627                                  ifexists: FileInfo => T,
628                                  ifmissing: => T): T =
629       (try { statfn(file.getPath) }
630        catch { case SystemError(ENOENT, _) => null }) match {
631         case null => ifmissing
632         case st => ifexists(st)
633       };
634     def exists_! : Boolean = statish(stat _, _ => true, false);
635     def isfifo_! : Boolean = statish(stat _, _.ftype == FIFO, false);
636     def isblk_! : Boolean = statish(stat _, _.ftype == BLK, false);
637     def isdir_! : Boolean = statish(stat _, _.ftype == DIR, false);
638     def ischr_! : Boolean = statish(stat _, _.ftype == CHR, false);
639     def isreg_! : Boolean = statish(stat _, _.ftype == REG, false);
640     def islnk_! : Boolean = statish(lstat _, _.ftype == LNK, false);
641     def issock_! : Boolean = statish(stat _, _.ftype == SOCK, false);
642
643     /* Slightly more cooked file operations. */
644     def remove_!() {
645       /* Delete a file, or directory, whatever it is. */
646       try { unlink_!(); return; }
647       catch {
648         case SystemError(ENOENT, _) => return;
649         case SystemError(EISDIR, _) =>
650           try { rmdir_!(); return; }
651           catch { case SystemError(ENOENT, _) => return; }
652       }
653     }
654
655     def rmTree() {
656       /* Delete a thing recursively. */
657       def walk(f: File) {
658         if (f.isdir_!) f.foreachFile(walk _);
659         f.remove_!();
660       }
661       walk(file);
662     }
663
664     def mkdirNew_!() {
665       /* Make a directory if there's nothing there already. */
666       try { mkdir_!(); }
667       catch { case SystemError(EEXIST, _) => ok; }
668     }
669
670     /* File locking. */
671     def lock_!(flags: Int): FileLock = new FileLock(file.getPath, flags);
672     def lock_!(): FileLock = lock_!(LKF_EXCL | 0x1b6);
673     def withLock[T](flags: Int)(body: => T): T = {
674       val lk = lock_!(flags);
675       try { body } finally { lk.close(); }
676     }
677     def withLock[T](body: => T): T = withLock(LKF_EXCL | 0x1b6) { body };
678
679     /* Opening files.  Again, I'm surprised this isn't here already. */
680     def open(): FileInputStream = new FileInputStream(file);
681     def openForOutput(): FileOutputStream = new FileOutputStream(file);
682     def reader(): BufferedReader =
683       new BufferedReader(new InputStreamReader(open()));
684     def writer(): BufferedWriter =
685       new BufferedWriter(new OutputStreamWriter(openForOutput()));
686     def withInput[T](body: FileInputStream => T): T = {
687       val in = open();
688       try { body(in) }
689       finally { in.close(); }
690     }
691     def withOutput[T](body: FileOutputStream => T): T = {
692       val out = openForOutput();
693       try { body(out) } finally { out.close(); }
694     }
695     def withReader[T](body: BufferedReader => T): T = withInput { in =>
696       body(new BufferedReader(new InputStreamReader(in)))
697     };
698     def withWriter[T](body: BufferedWriter => T): T = withOutput { out =>
699       val w = new BufferedWriter(new OutputStreamWriter(out));
700       /* Do this the hard way, so that we flush the `BufferedWriter'. */
701       try { body(w) } finally { w.close(); }
702     }
703   }
704 }
705 import FileImplicits._;
706
707 /*----- Miscellaneous file hacks ------------------------------------------*/
708
709 def freshFile(d: File): File = {
710   /* Return the name of a freshly created file in directory D. */
711
712   val buf = new Array[Byte](6);
713   val b = new StringBuilder;
714
715   loop[File] { exit =>
716     /* Keep going until we find a fresh one. */
717
718     /* Provide a prefix.  Mostly this is to prevent the file starting with
719      * an unfortunate character like `-'.
720      */
721     b ++= "tmp.";
722
723     /* Generate some random bytes. */
724     rng.nextBytes(buf);
725
726     /* Now turn the bytes into a filename.  This is a cheesy implementation
727      * of Base64 encoding.
728      */
729     var a = 0;
730     var n = 0;
731
732     for (x <- buf) {
733       a = (a << 8) | x; n += 8;
734       while (n >= 6) {
735         val y = (a >> n - 6)&0x3f; n -= 6;
736         b += (if (y < 26) 'A' + y
737               else if (y < 52) 'a' + (y - 26)
738               else if (y < 62) '0' + (y - 52)
739               else if (y == 62) '+'
740               else '-').toChar;
741       }
742     }
743
744     /* Make the filename, and try to create the file.  If we succeed, we
745      * win.
746      */
747     val f = d/b.result; b.clear();
748     try { f.mkfile_!(); exit(f); }
749     catch { case SystemError(EEXIST, _) => ok; }
750   }
751 }
752
753 /*----- Running a command -------------------------------------------------*/
754
755 private val devnull = new File("/dev/null");
756
757 private def captureStream(in: InputStream, out: StringBuilder) {
758   /* Capture the INSTREAM's contents in a string. */
759
760   for ((buf, n) <- blocks(new InputStreamReader(in)))
761     out.appendAll(buf, 0, n);
762 }
763
764 class SubprocessFailed(val cmd: Seq[String], rc: Int, stderr: String)
765         extends Exception {
766   override def getMessage(): String =
767     s"process (${quoteTokens(cmd)}) failed (rc = $rc):\n" + stderr
768 }
769
770 def runCommand(cmd: String*): (String, String) = {
771   /* Run a command, returning its stdout and stderr. */
772
773   withCleaner { clean =>
774
775     /* Create the child process and pick up the ends of its streams. */
776     val pb = new ProcessBuilder(cmd.asJava);
777     val kid = pb.start(); clean { kid.destroy(); }
778     kid.getOutputStream.close();
779     val out = kid.getInputStream(); clean { out.close(); }
780     val err = kid.getErrorStream(); clean { err.close(); }
781
782     /* Capture the output in threads, so we don't block.  Also, wait for the
783      * child to complete.  Amazingly, messing with threads here isn't too
784      * much of a disaster.
785      */
786     val bout, berr = new StringBuilder;
787     val rdout = thread("capture process stdout", daemon = false) {
788       captureStream(out, bout);
789     }
790     val rderr = thread("capture process stderr", daemon = false) {
791       captureStream(err, berr);
792     }
793     val wait = thread("await process exit", daemon = false) {
794       kid.waitFor();
795     }
796     rdout.join(); rderr.join(); wait.join();
797
798     /* Check the exit status. */
799     val rc = kid.exitValue;
800     if (rc != 0) throw new SubprocessFailed(cmd, rc, berr.result);
801
802     /* We're all done. */
803     return (bout.result, berr.result);
804   }
805 }
806
807 /*----- Interrupt triggers ------------------------------------------------*/
808
809 private val triggerLock = new Object;
810 private final val maxTriggers = 2;
811 private var nTriggers = 0;
812 private var triggers: List[Wrapper] = Nil;
813
814 @native protected def make_trigger(): Wrapper;
815 @native protected def destroy_trigger(trig: Wrapper);
816 @native protected def reset_trigger(trig: Wrapper);
817 @native protected def trigger(trig: Wrapper);
818
819 private def getTrigger(): Wrapper = {
820   triggerLock synchronized {
821     if (nTriggers == 0)
822       make_trigger()
823     else {
824       val trig = triggers.head;
825       triggers = triggers.tail;
826       nTriggers -= 1;
827       trig
828     }
829   }
830 }
831
832 private def putTrigger(trig: Wrapper) {
833   reset_trigger(trig);
834   triggerLock synchronized {
835     if (nTriggers >= maxTriggers)
836       destroy_trigger(trig);
837     else {
838       triggers ::= trig;
839       nTriggers += 1;
840     }
841   }
842 }
843
844 private def withTrigger[T](body: Wrapper => T): T = {
845   val trig = getTrigger();
846   try { body(trig) }
847   finally { putTrigger(trig); }
848 }
849
850 def interruptWithTrigger[T](body: Wrapper => T): T = {
851   /* interruptWithTrigger { TRIG => BODY }
852    *
853    * Execute BODY and return its result.  If the thread receives an
854    * interrupt, the trigger TRIG will be pulled.  See `interruptably' for the
855    * full semantics.
856    */
857
858   withTrigger { trig =>
859     interruptably { body(trig) } onInterrupt { trigger(trig); }
860   };
861 }
862
863 /*----- Glue for the VPN server -------------------------------------------*/
864
865 /* The lock class.  This is only a class because they're much easier to find
866  * than loose objects through JNI.
867  */
868 private class ServerLock;
869
870 /* Exceptions. */
871 class NameResolutionException(msg: String) extends Exception(msg);
872 class InitializationException(msg: String) extends Exception(msg);
873
874 /* Primitive operations. */
875 @native protected def open_tun(): Int;
876 @native protected def base_init();
877 @native protected def setup_resolver();
878 @native def load_keys(priv: CString, pub: CString, tag: CString);
879 @native def unload_keys();
880 @native def bind(host: CString, svc: CString);
881 @native def unbind();
882 @native def mark(seq: Int);
883 @native def run();
884 @native protected def send(buf: CString, start: Int, len: Int,
885                            trig: Wrapper);
886 @native protected def recv(buf: CString, start: Int, len: Int,
887                            trig: Wrapper): Int;
888
889 base_init();
890 setup_resolver();
891
892 /* Tunnel descriptor plumbing. */
893 val pending = HashMap[String, Int]();
894
895 def getTunnelFd(peer: CString): Int =
896   pending synchronized { pending(peer.toJString) };
897 def storeTunnelFd(peer: String, fd: Int)
898   { pending synchronized { pending(peer) = fd; } }
899 def withdrawTunnelFd(peer: String)
900   { pending synchronized { pending -= peer; } }
901 def withTunnelFd[T](peer: String, fd: Int)(body: => T): T = {
902   storeTunnelFd(peer, fd);
903   try { body } finally { withdrawTunnelFd(peer); }
904 }
905
906 /* Server I/O. */
907 lazy val serverInput: InputStream = new InputStream {
908   override def read(): Int = {
909     val buf = new Array[Byte](1);
910     val n = read(buf, 0, 1);
911     if (n < 0) -1 else buf(0)&0xff;
912   }
913   override def read(buf: Array[Byte]): Int =
914     read(buf, 0, buf.length);
915   override def read(buf: Array[Byte], start: Int, len: Int) =
916     interruptWithTrigger { trig => recv(buf, start, len, trig); };
917   override def close() { }
918 }
919
920 lazy val serverOutput: OutputStream = new OutputStream {
921   override def write(b: Int) { write(Array[Byte](b.toByte), 0, 1); }
922   override def write(buf: Array[Byte]) { write(buf, 0, buf.length); }
923   override def write(buf: Array[Byte], start: Int, len: Int)
924   { interruptWithTrigger { trig => send(buf, start, len, trig); } }
925   override def close() { }
926 }
927
928 /*----- Crypto-library hacks ----------------------------------------------*/
929
930 @native def hashsz(hash: String): Int;
931       /* Return the output hash size for the named HASH function, or -1. */
932
933 /*----- That's all, folks -------------------------------------------------*/
934
935 }