chiark / gitweb /
Makefile: Move all artifacts into a subdirectory.
[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.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("toy");
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 Type private[Errno](tag: String, val code: Int, id: Int)
179           extends 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 Type("<unknown>", code, code);
186
187   private def err(tag: String, code: Int): Type = {
188     /* Construct an error symbol given its tag string and a code number. */
189
190     if (code < 0) new Type(tag, code, code)
191     else if (seen contains code) new Type(tag, code, nextwrong)
192     else { seen += code; new Type(tag, code, code) }
193   }
194   private def err(tag: String): Type =
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.{Type => 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   val FIFO, CHR, DIR, BLK, REG, LNK, SOCK, UNK = Value;
443   type Type = Value;
444 }
445 import FileInfo._;
446
447 class FileInfo private[this](val devMajor: Int, val devMinor: Int,
448                              val ino: Long, val mode: Int, val nlink: Int,
449                              val uid: Int, val gid: Int,
450                              _rdevMinor: Int, _rdevMajor: Int,
451                              val size: Long,
452                              val blksize: Int, val blocks: Long,
453                              val atime: Date, val mtime: Date,
454                              val ctime: Date) {
455   /* Information about a file.  This is constructed directly from native
456    * code.
457    */
458
459   private def this(devMajor: Int, devMinor: Int, ino: Long,
460                    mode: Int, nlink: Int, uid: Int, gid: Int,
461                    rdevMinor: Int, rdevMajor: Int,
462                    size: Long, blksize: Int, blocks: Long,
463                    atime: Long, mtime: Long, ctime: Long) {
464     /* Lightly cook the values from the underlying `struct stat'. */
465
466     this(devMajor, devMinor, ino, mode, nlink, uid, gid,
467          rdevMajor, rdevMinor, size, blksize, blocks,
468          new Date(atime), new Date(mtime), new Date(ctime));
469   }
470
471   /* Return the file permissions only. */
472   def perms: Int = mode&0xfff;
473
474   /* Return the filetype, as a `FileInfo.Type'. */
475   def ftype: Type = (mode&S_IFMT) match {
476     case S_IFIFO => FIFO
477     case S_IFCHR => CHR
478     case S_IFDIR => DIR
479     case S_IFBLK => BLK
480     case S_IFREG => REG
481     case S_IFLNK => LNK
482     case S_IFSOCK => SOCK
483     case _ => UNK
484   }
485
486   private[this] def mustBeDevice() {
487     /* Insist that you only ask for `rdev' fields on actual device nodes. */
488     ftype match {
489       case CHR | BLK => ok;
490       case _ => throw new IllegalArgumentException("Object is not a device");
491     }
492   }
493
494   /* Query the device-node numbers. */
495   def rdevMajor: Int = { mustBeDevice(); _rdevMajor }
496   def rdevMinor: Int = { mustBeDevice(); _rdevMinor }
497 }
498
499 /*----- Listing directories -----------------------------------------------*/
500
501 /* Primitive operations. */
502 @native protected def opendir(path: CString): Wrapper;
503 @native protected def readdir(path: CString, dir: Wrapper): CString;
504 @native protected def closedir(path: CString, dir: Wrapper);
505
506 protected abstract class BaseDirIterator[T](cpath: CString)
507         extends LookaheadIterator[T] with Closeable {
508   /* The underlying machinery for directory iterators.
509    *
510    * Subclasses must define `mangle' to convert raw filenames into a T.
511    * We keep track of the path C-string, because we need to keep passing that
512    * back to C for inclusion in error messages.  Recording higher-level
513    * things is left for subclasses.
514    */
515
516   /* Constructors from more convenient types. */
517   def this(path: String) { this(path.toCString); }
518   def this(dir: File) { this(dir.getPath); }
519
520   /* Cleaning up after ourselves. */
521   override def close() { closedir(cpath, dir); }
522   override protected def finalize() { super.finalize(); close(); }
523
524   /* Subclass responsibility. */
525   protected def mangle(file: String): T;
526
527   /* Main machinery. */
528   private[this] val dir = opendir(cpath);
529   override protected def fetch(): Option[T] = readdir(cpath, dir) match {
530     case null => None
531     case f => f.toJString match {
532       case "." | ".." => fetch()
533       case jf => Some(mangle(jf))
534     }
535   }
536 }
537
538 class DirIterator(val path: String) extends BaseDirIterator[String](path) {
539   /* Iterator over the basenames of files in a directory. */
540
541   def this(dir: File) { this(dir.getPath); }
542
543   override protected def mangle(file: String): String = file;
544 }
545
546 class DirFilesIterator private[this](val dir: File, cpath: CString)
547         extends BaseDirIterator[File](cpath) {
548   /* Iterator over full `File' objects in a directory. */
549
550   def this(dir: File) { this(dir, dir.getPath.toCString); }
551   def this(path: String) { this(new File(path), path.toCString); }
552
553   override protected def mangle(file: String): File = new File(dir, file);
554 }
555
556 /*----- File locking ------------------------------------------------------*/
557
558 /* Primitive operations.  The low `mode' bits are for the lock file if we
559  * have to create it.
560  */
561 final val LKF_EXCL = 0x1000;
562 final val LKF_WAIT = 0x2000;
563 @native protected def lock(path: CString, mode: Int): Wrapper;
564 @native protected def unlock(lock: Wrapper);
565
566 class FileLock(path: String, flags: Int) extends Closeable {
567   /* A class which represents a held lock on a file. */
568
569   /* Constructors.  The default is to take an exclusive lock or fail
570    * immediately.
571    */
572   def this(file: File, flags: Int) { this(file.getPath, flags); }
573   def this(path: String) { this(path, LKF_EXCL | 0x1b6); }
574   def this(file: File) { this(file.getPath, LKF_EXCL | 0x1b6); }
575
576   /* The low-level lock object, actually a file descriptor. */
577   private[this] val lk = lock(path.toCString, flags);
578
579   /* Making sure things get cleaned up. */
580   override def close() { unlock(lk); }
581   override protected def finalize() { super.finalize(); close(); }
582 }
583
584 /*----- File implicits ----------------------------------------------------*/
585
586 object FileImplicits {
587   implicit class FileOps(file: File) {
588     /* Augment `File' with operations which throw informative (if low-level
589      * and system-specific) exceptions rather than returning unhelpful
590      * win/lose booleans.  These have names ending with `_!' because they
591      * might explode.
592      *
593      * And some other useful methods.
594      */
595
596     /* Constructing names of files in a directory.  Honestly, I'm surprised
597      * there isn't a method for this already.
598      */
599     def +(sub: String): File = new File(file, sub);
600
601     /* Simple file operations. */
602     def unlink_!() { unlink(file.getPath); }
603     def rmdir_!() { rmdir(file.getPath); }
604     def mkdir_!(mode: Int) { mkdir(file.getPath, mode); }
605     def mkdir_!() { mkdir_!(0x1ff); }
606     def mkfile_!(mode: Int) { mkfile(file.getPath, mode); }
607     def mkfile_!() { mkfile_!(0x1b6); }
608     def rename_!(to: File) { rename(file.getPath, to.getPath); }
609
610     /* Listing directories. */
611     def withFilesIterator[T](body: DirFilesIterator => T): T = {
612       val iter = new DirFilesIterator(file.getPath);
613       try { body(iter) } finally { iter.close(); }
614     }
615     def foreachFile(fn: File => Unit) { withFilesIterator(_.foreach(fn)) }
616     def files_! : Seq[File] = withFilesIterator { _.toSeq };
617
618     /* Low-level lFile information. */
619     def stat_! : FileInfo = stat(file.getPath);
620     def lstat_! : FileInfo = lstat(file.getPath);
621
622     /* Specific file-status queries. */
623     private[this] def statish[T](statfn: String => FileInfo,
624                                  ifexists: FileInfo => T,
625                                  ifmissing: => T): T =
626       (try { statfn(file.getPath) }
627        catch { case SystemError(ENOENT, _) => null }) match {
628         case null => ifmissing
629         case st => ifexists(st)
630       };
631     def exists_! : Boolean = statish(stat _, _ => true, false);
632     def isfifo_! : Boolean = statish(stat _, _.ftype == FIFO, false);
633     def isblk_! : Boolean = statish(stat _, _.ftype == BLK, false);
634     def isdir_! : Boolean = statish(stat _, _.ftype == DIR, false);
635     def ischr_! : Boolean = statish(stat _, _.ftype == CHR, false);
636     def isreg_! : Boolean = statish(stat _, _.ftype == REG, false);
637     def islnk_! : Boolean = statish(lstat _, _.ftype == LNK, false);
638     def issock_! : Boolean = statish(stat _, _.ftype == SOCK, false);
639
640     def remove_!() {
641       /* Delete a file, or directory, whatever it is. */
642       while (true) {
643         try { unlink_!(); return; }
644         catch {
645           case SystemError(ENOENT, _) => return;
646           case SystemError(EISDIR, _) => ok;
647         }
648         try { rmdir_!(); return; }
649         catch {
650           case SystemError(ENOENT, _) => return;
651           case SystemError(ENOTDIR, _) => ok;
652         }
653       }
654     }
655
656     def rmTree() {
657       /* Delete a thing recursively. */
658       def walk(f: File) {
659         if (f.isdir_!) f.foreachFile(walk _);
660         f.remove_!();
661       }
662       walk(file);
663     }
664
665     /* File locking. */
666     def lock_!(flags: Int): FileLock = new FileLock(file.getPath, flags);
667     def lock_!(): FileLock = lock_!(LKF_EXCL | 0x1b6);
668     def withLock[T](flags: Int)(body: => T): T = {
669       val lk = lock_!(flags);
670       try { body } finally { lk.close(); }
671     }
672     def withLock[T](body: => T): T = withLock(LKF_EXCL | 0x1b6) { body };
673
674     /* Opening files.  Again, I'm surprised this isn't here already. */
675     def open(): FileInputStream = new FileInputStream(file);
676     def openForOutput(): FileOutputStream = new FileOutputStream(file);
677     def reader(): BufferedReader =
678       new BufferedReader(new InputStreamReader(open()));
679     def writer(): BufferedWriter =
680       new BufferedWriter(new OutputStreamWriter(openForOutput()));
681     def withInput[T](body: FileInputStream => T): T = {
682       val in = open();
683       try { body(in) }
684       finally { in.close(); }
685     }
686     def withOutput[T](body: FileOutputStream => T): T = {
687       val out = openForOutput();
688       try { body(out) } finally { out.close(); }
689     }
690     def withReader[T](body: BufferedReader => T): T = withInput { in =>
691       body(new BufferedReader(new InputStreamReader(in)))
692     };
693     def withWriter[T](body: BufferedWriter => T): T = withOutput { out =>
694       val w = new BufferedWriter(new OutputStreamWriter(out));
695       /* Do this the hard way, so that we flush the `BufferedWriter'. */
696       try { body(w) } finally { w.close(); }
697     }
698   }
699 }
700 import FileImplicits._;
701
702 /*----- Miscellaneous file hacks ------------------------------------------*/
703
704 def freshFile(d: File): File = {
705   /* Return the name of a freshly created file in directory D. */
706
707   val buf = new Array[Byte](6);
708   val b = new StringBuilder;
709
710   loop[File] { exit =>
711     /* Keep going until we find a fresh one. */
712
713     /* Provide a prefix.  Mostly this is to prevent the file starting with
714      * an unfortunate character like `-'.
715      */
716     b ++= "tmp.";
717
718     /* Generate some random bytes. */
719     rng.nextBytes(buf);
720
721     /* Now turn the bytes into a filename.  This is a cheesy implementation
722      * of Base64 encoding.
723      */
724     var a = 0;
725     var n = 0;
726
727     for (x <- buf) {
728       a = (a << 8) | x; n += 8;
729       while (n >= 6) {
730         val y = (a >> n - 6)&0x3f; n -= 6;
731         b += (if (y < 26) 'A' + y
732               else if (y < 52) 'a' + (y - 26)
733               else if (y < 62) '0' + (y - 52)
734               else if (y == 62) '+'
735               else '-').toChar;
736       }
737     }
738
739     /* Make the filename, and try to create the file.  If we succeed, we
740      * win.
741      */
742     val f = new File(d, b.result); b.clear();
743     try { f.mkfile_!(); exit(f); }
744     catch { case SystemError(EEXIST, _) => ok; }
745   }
746 }
747
748 /*----- Running a command -------------------------------------------------*/
749
750 private val devnull = new File("/dev/null");
751
752 private def captureStream(in: InputStream, out: StringBuilder) {
753   /* Capture the INSTREAM's contents in a string. */
754
755   for ((buf, n) <- blocks(new InputStreamReader(in)))
756     out.appendAll(buf, 0, n);
757 }
758
759 class SubprocessFailed(val cmd: Seq[String], rc: Int, stderr: String)
760         extends Exception {
761   override def getMessage(): String =
762     s"process (${quoteTokens(cmd)}) failed (rc = $rc):\n" + stderr
763 }
764
765 def runCommand(cmd: String*): (String, String) = {
766   /* Run a command, returning its stdout and stderr. */
767
768   withCleaner { clean =>
769
770     /* Create the child process and pick up the ends of its streams. */
771     val pb = new ProcessBuilder(cmd.asJava).redirectInput(devnull);
772     val kid = pb.start(); clean { kid.destroy(); }
773     val out = kid.getInputStream(); clean { out.close(); }
774     val err = kid.getErrorStream(); clean { err.close(); }
775
776     /* Capture the output in threads, so we don't block.  Also, wait for the
777      * child to complete.  Amazingly, messing with threads here isn't too
778      * much of a disaster.
779      */
780     val bout, berr = new StringBuilder;
781     val rdout = thread("capture process stdout", daemon = false) {
782       captureStream(out, bout);
783     }
784     val rderr = thread("capture process stderr", daemon = false) {
785       captureStream(err, berr);
786     }
787     val wait = thread("await process exit", daemon = false) {
788       kid.waitFor();
789     }
790     rdout.join(); rderr.join(); wait.join();
791
792     /* Check the exit status. */
793     val rc = kid.exitValue;
794     if (rc != 0) throw new SubprocessFailed(cmd, rc, berr.result);
795
796     /* We're all done. */
797     return (bout.result, berr.result);
798   }
799 }
800
801 /*----- Connecting to a server --------------------------------------------*/
802
803 /* Primitive operations. */
804 final val CF_CLOSERD = 1;
805 final val CF_CLOSEWR = 2;
806 final val CF_CLOSEMASK = CF_CLOSERD | CF_CLOSEWR;
807 @native protected def connect(path: CString): Wrapper;
808 @native protected def send(conn: Wrapper, buf: CString,
809                            start: Int, len: Int);
810 @native protected def recv(conn: Wrapper, buf: CString,
811                            start: Int, len: Int): Int;
812 @native def closeconn(conn: Wrapper, how: Int);
813
814 class Connection(path: String) extends Closeable {
815
816   /* The underlying primitive connection. */
817   private[this] val conn = connect(path.toCString);
818
819   /* Alternative constructors. */
820   def this(file: File) { this(file.getPath); }
821
822   /* Cleanup.*/
823   override def close() { closeconn(conn, CF_CLOSEMASK); }
824   override protected def finalize() { super.finalize(); close(); }
825
826   class Input private[Connection] extends InputStream {
827     /* An input stream which reads from the connection. */
828
829     override def read(): Int = {
830       val buf = new Array[Byte](1);
831       val n = read(buf, 0, 1);
832       if (n < 0) -1 else buf(0)&0xff;
833     }
834     override def read(buf: Array[Byte]): Int =
835       read(buf, 0, buf.length);
836     override def read(buf: Array[Byte], start: Int, len: Int) =
837       recv(conn, buf, start, len);
838     override def close() { closeconn(conn, CF_CLOSERD); }
839   }
840   lazy val input = new Input;
841
842   class Output private[Connection] extends OutputStream {
843     /* An output stream which writes to the connection. */
844
845     override def write(b: Int) { write(Array[Byte](b.toByte), 0, 1); }
846     override def write(buf: Array[Byte]) { write(buf, 0, buf.length); }
847     override def write(buf: Array[Byte], start: Int, len: Int)
848       { send(conn, buf, start, len); }
849     override def close() { closeconn(conn, CF_CLOSEWR); }
850   }
851   lazy val output = new Output;
852 }
853
854 /*----- Crypto-library hacks ----------------------------------------------*/
855
856 @native def hashsz(hash: String): Int;
857       /* Return the output hash size for the named HASH function, or -1. */
858
859 /*----- That's all, folks -------------------------------------------------*/
860
861 }