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