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