chiark / gitweb /
c012d1f3bd303ee2e05775b1b1f2a1926b3dd076
[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.mutable.HashSet;
31
32 import java.io.{Closeable, File};
33 import java.nio.{ByteBuffer, CharBuffer};
34 import java.nio.charset.Charset;
35 import java.util.Date;
36
37 /*----- Some magic for C strings ------------------------------------------*/
38
39 type CString = Array[Byte];
40
41 /* We do this by hand, rather than relying on the JNI's built-in conversions,
42  * because we use the default encoding taken from the locale settings, rather
43  * than the ridiculous `modified UTF-8' which is (a) insensitive to the
44  * user's chosen locale and (b) not actually UTF-8 either.
45  */
46
47 class InvalidCStringException(msg: String) extends Exception(msg);
48
49 object StringImplicits {
50   implicit class ConvertJStringToCString(s: String) {
51     /* Magic to convert a string into a C string (null-terminated bytes). */
52
53     def toCString: CString = {
54       /* Convert the receiver to a C string. */
55
56       val enc = Charset.defaultCharset.newEncoder;
57       val in = CharBuffer.wrap(s);
58       var sz: Int = (s.length*enc.averageBytesPerChar + 1).toInt;
59       var out = ByteBuffer.allocate(sz);
60
61       while (true) {
62         /* If there's still stuff to encode, then encode it.  Otherwise,
63          * there must be some dregs left in the encoder, so flush them out.
64          */
65         val r = if (in.hasRemaining) enc.encode(in, out, true)
66                 else enc.flush(out);
67
68         /* Sift through the wreckage to figure out what to do. */
69         if (r.isError) r.throwException();
70         else if (r.isOverflow) {
71           /* No space in the buffer.  Make it bigger. */
72
73           sz *= 2;
74           val newout = ByteBuffer.allocate(sz);
75           out.flip(); newout.put(out);
76           out = newout;
77         } else if (r.isUnderflow) {
78           /* All done.  Check that there are no unexpected zero bytes -- so
79            * this will indeed be a valid C string -- and convert into a byte
80            * array that the C code will be able to pick apart.
81            */
82
83           out.flip(); val n = out.limit; val u = out.array;
84           if ({val z = u.indexOf(0); 0 <= z && z < n})
85             throw new InvalidCStringException("null byte in encoding");
86           val v = new Array[Byte](n + 1);
87           out.array.copyToArray(v, 0, n);
88           v(n) = 0;
89           return v;
90         }
91       }
92
93       /* Placate the type checker. */
94       unreachable("unreachable");
95     }
96   }
97
98   implicit class ConvertCStringToJString(v: CString) {
99     /* Magic to convert a C string into a `proper' string. */
100
101     def toJString: String = {
102       /* Convert the receiver to a C string.
103        *
104        * We do this by hand, rather than relying on the JNI's built-in
105        * conversions, because we use the default encoding taken from the
106        * locale settings, rather than the ridiculous `modified UTF-8' which
107        * is (a) insensitive to the user's chosen locale and (b) not actually
108        * UTF-8 either.
109        */
110
111       val inlen = v.indexOf(0) match {
112         case -1 => v.length
113         case n => n
114       }
115       val dec = Charset.defaultCharset.newDecoder;
116       val in = ByteBuffer.wrap(v, 0, inlen);
117       dec.decode(in).toString
118     }
119   }
120 }
121 import StringImplicits._;
122
123 /*----- Main code ---------------------------------------------------------*/
124
125 /* Import the native code library. */
126 System.loadLibrary("toy");
127
128 /* Exception indicating that a wrapped native object has been clobbered. */
129 class NativeObjectTypeException(msg: String) extends RuntimeException(msg);
130 type Wrapper = Array[Byte];
131
132 /*----- Error codes -------------------------------------------------------*/
133
134 protected case class ErrorEntry(val tag: String, val err: Int);
135 @native protected def errtab: Array[ErrorEntry];
136 @native protected def strerror(err: Int): CString;
137
138 object Errno extends Enumeration {
139   private val tagmap = {
140     val b = Map.newBuilder[String, Int];
141     for (ErrorEntry(tag, err) <- errtab) b += tag -> err;
142     b.result
143   }
144   private var wrong = -255;
145   private val seen = HashSet[Int]();
146
147   class ErrnoVal private[Errno](tag: String, val code: Int, id: Int)
148         extends Val(id, tag) {
149     def message: String = strerror(code).toJString;
150   }
151
152   private def err(tag: String, code: Int): ErrnoVal = {
153     if (seen contains code) { wrong -= 1; new ErrnoVal(tag, code, wrong) }
154     else { seen += code; new ErrnoVal(tag, code, code) }
155   }
156   private def err(tag: String): ErrnoVal = err(tag, tagmap(tag));
157
158   val OK = err("OK", 0);
159
160   /*
161      ;;; The errno name table is very boring to type.  To make life less
162      ;;; awful, put the errno names in this list and evaluate the code to
163      ;;; get Emacs to regenerate it.
164
165      (let ((errors '(EPERM ENOENT ESRCH EINTR EIO ENXIO E2BIG ENOEXEC EBADF
166                      ECHILD EAGAIN ENOMEM EACCES EFAULT ENOTBLK EBUSY EEXIST
167                      EXDEV ENODEV ENOTDIR EISDIR EINVAL ENFILE EMFILE ENOTTY
168                      ETXTBSY EFBIG ENOSPC ESPIPE EROFS EMLINK EPIPE EDOM
169                      ERANGE
170
171                      EDEADLK ENAMETOOLONG ENOLCK ENOSYS ENOTEMPTY ELOOP
172                      EWOULDBLOCK ENOMSG EIDRM ECHRNG EL2NSYNC EL3HLT EL3RST
173                      ELNRNG EUNATCH ENOCSI EL2HLT EBADE EBADR EXFULL ENOANO
174                      EBADRQC EBADSLT EDEADLOCK EBFONT ENOSTR ENODATA ETIME
175                      ENOSR ENONET ENOPKG EREMOTE ENOLINK EADV ESRMNT ECOMM
176                      EPROTO EMULTIHOP EDOTDOT EBADMSG EOVERFLOW ENOTUNIQ
177                      EBADFD EREMCHG ELIBACC ELIBBAD ELIBSCN ELIBMAX ELIBEXEC
178                      EILSEQ ERESTART ESTRPIPE EUSERS ENOTSOCK EDESTADDRREQ
179                      EMSGSIZE EPROTOTYPE ENOPROTOOPT EPROTONOSUPPORT
180                      ESOCKTNOSUPPORT EOPNOTSUPP EPFNOSUPPORT EAFNOSUPPORT
181                      EADDRINUSE EADDRNOTAVAIL ENETDOWN ENETUNREACH ENETRESET
182                      ECONNABORTED ECONNRESET ENOBUFS EISCONN ENOTCONN
183                      ESHUTDOWN ETOOMANYREFS ETIMEDOUT ECONNREFUSED EHOSTDOWN
184                      EHOSTUNREACH EALREADY EINPROGRESS ESTALE EUCLEAN ENOTNAM
185                      ENAVAIL EISNAM EREMOTEIO EDQUOT ENOMEDIUM EMEDIUMTYPE
186                      ECANCELED ENOKEY EKEYEXPIRED EKEYREVOKED EKEYREJECTED
187                      EOWNERDEAD ENOTRECOVERABLE ERFKILL EHWPOISON)))
188        (save-excursion
189          (goto-char (point-min))
190          (search-forward (concat "***" "BEGIN errtab" "***"))
191          (beginning-of-line 2)
192          (delete-region (point)
193                         (progn
194                           (search-forward "***END***")
195                           (beginning-of-line)
196                           (point)))
197          (dolist (err errors)
198            (insert (format "  val %s = err(\"%s\");\n" err err)))))
199   */
200   /***BEGIN errtab***/
201   val EPERM = err("EPERM");
202   val ENOENT = err("ENOENT");
203   val ESRCH = err("ESRCH");
204   val EINTR = err("EINTR");
205   val EIO = err("EIO");
206   val ENXIO = err("ENXIO");
207   val E2BIG = err("E2BIG");
208   val ENOEXEC = err("ENOEXEC");
209   val EBADF = err("EBADF");
210   val ECHILD = err("ECHILD");
211   val EAGAIN = err("EAGAIN");
212   val ENOMEM = err("ENOMEM");
213   val EACCES = err("EACCES");
214   val EFAULT = err("EFAULT");
215   val ENOTBLK = err("ENOTBLK");
216   val EBUSY = err("EBUSY");
217   val EEXIST = err("EEXIST");
218   val EXDEV = err("EXDEV");
219   val ENODEV = err("ENODEV");
220   val ENOTDIR = err("ENOTDIR");
221   val EISDIR = err("EISDIR");
222   val EINVAL = err("EINVAL");
223   val ENFILE = err("ENFILE");
224   val EMFILE = err("EMFILE");
225   val ENOTTY = err("ENOTTY");
226   val ETXTBSY = err("ETXTBSY");
227   val EFBIG = err("EFBIG");
228   val ENOSPC = err("ENOSPC");
229   val ESPIPE = err("ESPIPE");
230   val EROFS = err("EROFS");
231   val EMLINK = err("EMLINK");
232   val EPIPE = err("EPIPE");
233   val EDOM = err("EDOM");
234   val ERANGE = err("ERANGE");
235   val EDEADLK = err("EDEADLK");
236   val ENAMETOOLONG = err("ENAMETOOLONG");
237   val ENOLCK = err("ENOLCK");
238   val ENOSYS = err("ENOSYS");
239   val ENOTEMPTY = err("ENOTEMPTY");
240   val ELOOP = err("ELOOP");
241   val EWOULDBLOCK = err("EWOULDBLOCK");
242   val ENOMSG = err("ENOMSG");
243   val EIDRM = err("EIDRM");
244   val ECHRNG = err("ECHRNG");
245   val EL2NSYNC = err("EL2NSYNC");
246   val EL3HLT = err("EL3HLT");
247   val EL3RST = err("EL3RST");
248   val ELNRNG = err("ELNRNG");
249   val EUNATCH = err("EUNATCH");
250   val ENOCSI = err("ENOCSI");
251   val EL2HLT = err("EL2HLT");
252   val EBADE = err("EBADE");
253   val EBADR = err("EBADR");
254   val EXFULL = err("EXFULL");
255   val ENOANO = err("ENOANO");
256   val EBADRQC = err("EBADRQC");
257   val EBADSLT = err("EBADSLT");
258   val EDEADLOCK = err("EDEADLOCK");
259   val EBFONT = err("EBFONT");
260   val ENOSTR = err("ENOSTR");
261   val ENODATA = err("ENODATA");
262   val ETIME = err("ETIME");
263   val ENOSR = err("ENOSR");
264   val ENONET = err("ENONET");
265   val ENOPKG = err("ENOPKG");
266   val EREMOTE = err("EREMOTE");
267   val ENOLINK = err("ENOLINK");
268   val EADV = err("EADV");
269   val ESRMNT = err("ESRMNT");
270   val ECOMM = err("ECOMM");
271   val EPROTO = err("EPROTO");
272   val EMULTIHOP = err("EMULTIHOP");
273   val EDOTDOT = err("EDOTDOT");
274   val EBADMSG = err("EBADMSG");
275   val EOVERFLOW = err("EOVERFLOW");
276   val ENOTUNIQ = err("ENOTUNIQ");
277   val EBADFD = err("EBADFD");
278   val EREMCHG = err("EREMCHG");
279   val ELIBACC = err("ELIBACC");
280   val ELIBBAD = err("ELIBBAD");
281   val ELIBSCN = err("ELIBSCN");
282   val ELIBMAX = err("ELIBMAX");
283   val ELIBEXEC = err("ELIBEXEC");
284   val EILSEQ = err("EILSEQ");
285   val ERESTART = err("ERESTART");
286   val ESTRPIPE = err("ESTRPIPE");
287   val EUSERS = err("EUSERS");
288   val ENOTSOCK = err("ENOTSOCK");
289   val EDESTADDRREQ = err("EDESTADDRREQ");
290   val EMSGSIZE = err("EMSGSIZE");
291   val EPROTOTYPE = err("EPROTOTYPE");
292   val ENOPROTOOPT = err("ENOPROTOOPT");
293   val EPROTONOSUPPORT = err("EPROTONOSUPPORT");
294   val ESOCKTNOSUPPORT = err("ESOCKTNOSUPPORT");
295   val EOPNOTSUPP = err("EOPNOTSUPP");
296   val EPFNOSUPPORT = err("EPFNOSUPPORT");
297   val EAFNOSUPPORT = err("EAFNOSUPPORT");
298   val EADDRINUSE = err("EADDRINUSE");
299   val EADDRNOTAVAIL = err("EADDRNOTAVAIL");
300   val ENETDOWN = err("ENETDOWN");
301   val ENETUNREACH = err("ENETUNREACH");
302   val ENETRESET = err("ENETRESET");
303   val ECONNABORTED = err("ECONNABORTED");
304   val ECONNRESET = err("ECONNRESET");
305   val ENOBUFS = err("ENOBUFS");
306   val EISCONN = err("EISCONN");
307   val ENOTCONN = err("ENOTCONN");
308   val ESHUTDOWN = err("ESHUTDOWN");
309   val ETOOMANYREFS = err("ETOOMANYREFS");
310   val ETIMEDOUT = err("ETIMEDOUT");
311   val ECONNREFUSED = err("ECONNREFUSED");
312   val EHOSTDOWN = err("EHOSTDOWN");
313   val EHOSTUNREACH = err("EHOSTUNREACH");
314   val EALREADY = err("EALREADY");
315   val EINPROGRESS = err("EINPROGRESS");
316   val ESTALE = err("ESTALE");
317   val EUCLEAN = err("EUCLEAN");
318   val ENOTNAM = err("ENOTNAM");
319   val ENAVAIL = err("ENAVAIL");
320   val EISNAM = err("EISNAM");
321   val EREMOTEIO = err("EREMOTEIO");
322   val EDQUOT = err("EDQUOT");
323   val ENOMEDIUM = err("ENOMEDIUM");
324   val EMEDIUMTYPE = err("EMEDIUMTYPE");
325   val ECANCELED = err("ECANCELED");
326   val ENOKEY = err("ENOKEY");
327   val EKEYEXPIRED = err("EKEYEXPIRED");
328   val EKEYREVOKED = err("EKEYREVOKED");
329   val EKEYREJECTED = err("EKEYREJECTED");
330   val EOWNERDEAD = err("EOWNERDEAD");
331   val ENOTRECOVERABLE = err("ENOTRECOVERABLE");
332   val ERFKILL = err("ERFKILL");
333   val EHWPOISON = err("EHWPOISON");
334   /***end***/
335 }
336 import Errno.{Value => _, _};
337
338 object SystemError {
339   def apply(err: Errno.Value, what: String): SystemError =
340     new SystemError(err, what);
341   def unapply(e: Exception): Option[(Errno.Value, String)] = e match {
342     case e: SystemError => Some((e.err, e.what))
343     case _ => None
344   }
345 }
346
347 class SystemError private[this](val err: Errno.ErrnoVal, val what: String)
348         extends Exception {
349   def this(err: Errno.Value, what: String)
350     { this(err.asInstanceOf[Errno.ErrnoVal], what); }
351   private def this(err: Int, what: CString)
352     { this(Errno(err), what.toJString); }
353   override def getMessage(): String = s"$what: ${err.message}";
354 }
355
356 /*----- Basic file operations ---------------------------------------------*/
357
358 @native protected def unlink(path: CString);
359 def unlink(path: String) { unlink(path.toCString); }
360 @native protected def rmdir(path: CString);
361 def rmdir(path: String) { rmdir(path.toCString); }
362 @native protected def mkdir(path: CString, mode: Int);
363 def mkdir(path: String, mode: Int) { mkdir(path.toCString, mode); }
364 @native protected def mkfile(path: CString, mode: Int);
365 def mkfile(path: String, mode: Int) { mkfile(path.toCString, mode); }
366 @native protected def rename(from: CString, to: CString);
367 def rename(from: String, to: String)
368   { rename(from.toCString, to.toCString); }
369
370 /*----- File status information -------------------------------------------*/
371
372 /* These are the traditional values, but the C code carefully arranges to
373  * return them regardless of what your kernel actually thinks.
374  */
375 val S_IFMT = 0xf000;
376 val S_IFIFO = 0x1000;
377 val S_IFCHR = 0x2000;
378 val S_IFDIR = 0x4000;
379 val S_IFBLK = 0x6000;
380 val S_IFREG = 0x8000;
381 val S_IFLNK = 0xa000;
382 val S_IFSOCK = 0xc000;
383
384 @native protected def stat(path: CString): sys.FileInfo;
385 def stat(path: String): sys.FileInfo = stat(path.toCString);
386 @native protected def lstat(path: CString): sys.FileInfo;
387 def lstat(path: String): sys.FileInfo = lstat(path.toCString);
388
389 object FileInfo extends Enumeration {
390   val FIFO, CHR, DIR, BLK, REG, LNK, SOCK, UNK = Value;
391   type Type = Value;
392 }
393 import FileInfo._;
394
395
396 class FileInfo private[this](val devMajor: Int, val devMinor: Int,
397                              val ino: Long, val mode: Int, val nlink: Int,
398                              val uid: Int, val gid: Int,
399                              _rdevMinor: Int, _rdevMajor: Int,
400                              val size: Long,
401                              val blksize: Int, val blocks: Long,
402                              val atime: Date, val mtime: Date,
403                              val ctime: Date) {
404   private def this(devMajor: Int, devMinor: Int, ino: Long,
405                    mode: Int, nlink: Int, uid: Int, gid: Int,
406                    rdevMinor: Int, rdevMajor: Int,
407                    size: Long, blksize: Int, blocks: Long,
408                    atime: Long, mtime: Long, ctime: Long) {
409     this(devMajor, devMinor, ino, mode, nlink, uid, gid,
410          rdevMajor, rdevMinor, size, blksize, blocks,
411          new Date(atime), new Date(mtime), new Date(ctime));
412   }
413
414   def perms: Int = mode&0xfff;
415
416   def ftype: Type = (mode&S_IFMT) match {
417     case S_IFIFO => FIFO
418     case S_IFCHR => CHR
419     case S_IFDIR => DIR
420     case S_IFBLK => BLK
421     case S_IFREG => REG
422     case S_IFLNK => LNK
423     case S_IFSOCK => SOCK
424     case _ => UNK
425   }
426
427   private[this] def mustBeDevice() {
428     ftype match {
429       case CHR | BLK => ();
430       case _ => throw new IllegalArgumentException("Object is not a device");
431     }
432   }
433   def rdevMajor: Int = { mustBeDevice(); _rdevMajor }
434   def rdevMinor: Int = { mustBeDevice(); _rdevMinor }
435 }
436
437 /*----- Listing directories -----------------------------------------------*/
438
439 @native protected def opendir(path: CString): Wrapper;
440 @native protected def readdir(path: CString, dir: Wrapper): CString;
441 @native protected def closedir(path: CString, dir: Wrapper);
442
443 protected abstract class BaseDirIterator[T](cpath: CString)
444         extends LookaheadIterator[T] with Closeable {
445   def this(path: String) { this(path.toCString); }
446   def this(dir: File) { this(dir.getPath); }
447   override def close() { closedir(cpath, dir); }
448   override protected def finalize() { super.finalize(); close(); }
449   private[this] val dir = opendir(cpath);
450   protected def mangle(file: String): T;
451   override protected def fetch(): Option[T] = readdir(cpath, dir) match {
452     case null => None
453     case f => f.toJString match {
454       case "." | ".." => fetch()
455       case jf => Some(mangle(jf))
456     }
457   }
458 }
459
460 class DirIterator(val path: String) extends BaseDirIterator[String](path) {
461   def this(dir: File) { this(dir.getPath); }
462   override protected def mangle(file: String): String = file;
463 }
464
465 class DirFilesIterator private[this](val dir: File, cpath: CString)
466         extends BaseDirIterator[File](cpath) {
467   def this(dir: File) { this(dir, dir.getPath.toCString); }
468   def this(path: String) { this(new File(path), path.toCString); }
469   override protected def mangle(file: String): File = new File(dir, file);
470 }
471
472 /*----- File locking ------------------------------------------------------*/
473
474 val LKF_EXCL = 1;
475 val LKF_WAIT = 2;
476 @native protected def lock(path: CString, flags: Int): Wrapper;
477 @native protected def unlock(lock: Wrapper);
478
479 class FileLock(path: String, flags: Int) extends Closeable {
480   def this(file: File, flags: Int) { this(file.getPath, flags); }
481   def this(path: String) { this(path, LKF_EXCL); }
482   def this(file: File) { this(file.getPath, LKF_EXCL); }
483   private[this] val lk = lock(path.toCString, flags);
484   override def close() { unlock(lk); }
485   override protected def finalize() { super.finalize(); close(); }
486 }
487
488 /*----- File implicits ----------------------------------------------------*/
489
490 object FileImplicits {
491   implicit class FileOps(file: File) {
492
493     def unlink_!() { unlink(file.getPath); }
494     def rmdir_!() { rmdir(file.getPath); }
495     def mkdir_!(mode: Int) { mkdir(file.getPath, mode); }
496     def mkdir_!() { mkdir_!(0x1ff); }
497     def mkfile_!(mode: Int) { mkfile(file.getPath, mode); }
498     def mkfile_!() { mkfile_!(0x1b6); }
499
500     def withFilesIterator[T](body: DirFilesIterator => T): T = {
501       val iter = new DirFilesIterator(file.getPath);
502       try { body(iter) } finally { iter.close(); }
503     }
504     def files_! : Seq[File] = withFilesIterator { _.toSeq };
505
506     def stat_! : FileInfo = stat(file.getPath);
507     def lstat_! : FileInfo = lstat(file.getPath);
508
509     private[this] def statish[T](statfn: String => FileInfo,
510                                  ifexists: FileInfo => T,
511                                  ifmissing: => T): T =
512       (try { statfn(file.getPath) }
513        catch { case SystemError(ENOENT, _) => null }) match {
514         case null => ifmissing
515         case st => ifexists(st)
516       };
517     def exists_! : Boolean = statish(stat _, _ => true, false);
518     def isfifo_! : Boolean = statish(stat _, _.ftype == FIFO, false);
519     def isblk_! : Boolean = statish(stat _, _.ftype == BLK, false);
520     def isdir_! : Boolean = statish(stat _, _.ftype == DIR, false);
521     def ischr_! : Boolean = statish(stat _, _.ftype == CHR, false);
522     def isreg_! : Boolean = statish(stat _, _.ftype == REG, false);
523     def islnk_! : Boolean = statish(lstat _, _.ftype == LNK, false);
524     def issock_! : Boolean = statish(stat _, _.ftype == SOCK, false);
525
526     def remove_!() {
527       while (true) {
528         try { unlink_!(); return }
529         catch {
530           case SystemError(ENOENT, _) => return;
531           case SystemError(EISDIR, _) => ();
532         }
533         try { rmdir_!(); return }
534         catch {
535           case SystemError(ENOENT, _) => return;
536           case SystemError(ENOTDIR, _) => ();
537         }
538       }
539     }
540
541     def rmTree() {
542       def walk(f: File) {
543         if (f.isdir_!) f.withFilesIterator { _ foreach(walk _) };
544         f.remove_!();
545       }
546       walk(file);
547     }
548
549     def withLock[T](flags: Int)(body: => T): T = {
550       val lk = new FileLock(file.getPath, flags);
551       try { body } finally { lk.close(); }
552     }
553     def withLock[T](body: => T): T = withLock(LKF_EXCL) { body };
554   }
555 }
556 import FileImplicits._;
557
558 /*----- Miscellaneous file hacks ------------------------------------------*/
559
560 def freshFile(d: File): File = {
561   /* Return the name of a freshly created file in directory D. */
562
563   val buf = new Array[Byte](6);
564   val b = new StringBuilder;
565
566   while (true) {
567     /* Keep going until we find a fresh one. */
568
569     /* Provide a prefix.  Mostly this is to prevent the file starting with
570      * an unfortunate character like `-'.
571      */
572     b ++= "tmp.";
573
574     /* Generate some random bytes. */
575     rng.nextBytes(buf);
576
577     /* Now turn the bytes into a filename.  This is a cheesy implementation
578      * of Base64 encoding.
579      */
580     var a = 0;
581     var n = 0;
582
583     for (x <- buf) {
584       a = (a << 8) | x; n += 8;
585       while (n >= 6) {
586         val y = (a >> n - 6)&0x3f; n -= 6;
587         b += (if (y < 26) 'A' + y
588               else if (y < 52) 'a' + (y - 26)
589               else if (y < 62) '0' + (y - 52)
590               else if (y == 62) '+'
591               else '-').toChar;
592       }
593     }
594
595     /* Make the filename, and try to create the file.  If we succeed, we
596      * win.
597      */
598     val f = new File(d, b.result); b.clear();
599     try { f.mkfile_!(); return f; }
600     catch { case SystemError(EEXIST, _) => (); }
601   }
602
603   /* We shouldn't get here, but the type checker needs placating. */
604   unreachable("unreachable");
605 }
606
607 /*----- Connecting to a server --------------------------------------------*/
608
609 val CF_CLOSERD = 1;
610 val CF_CLOSEWR = 2;
611 val CF_CLOSEMASK = CF_CLOSERD | CF_CLOSEWR;
612 @native protected def connect(path: CString): Wrapper;
613 @native protected def send(conn: Wrapper, buf: CString,
614                            start: Int, len: Int);
615 @native protected def recv(conn: Wrapper, buf: CString,
616                            start: Int, len: Int): Int;
617 @native def closeconn(conn: Wrapper, how: Int);
618
619 class Connection(path: String) extends Closeable {
620   def this(file: File) { this(file.getPath); }
621   private[this] val conn = connect(path.toCString);
622   override def close() { closeconn(conn, CF_CLOSEMASK); }
623   override protected def finalize() { super.finalize(); close(); }
624
625   class InputStream private[Connection] extends java.io.InputStream {
626     override def read(): Int = {
627       val buf = new Array[Byte](1);
628       val n = read(buf, 0, 1);
629       if (n < 0) -1 else buf(0)&0xff;
630     }
631     override def read(buf: Array[Byte]): Int =
632       read(buf, 0, buf.length);
633     override def read(buf: Array[Byte], start: Int, len: Int) =
634       recv(conn, buf, start, len);
635     override def close() { closeconn(conn, CF_CLOSERD); }
636   }
637   lazy val input = new InputStream;
638
639   class OutputStream private[Connection] extends java.io.OutputStream {
640     override def write(b: Int) { write(Array[Byte](b.toByte), 0, 1); }
641     override def write(buf: Array[Byte]) { write(buf, 0, buf.length); }
642     override def write(buf: Array[Byte], start: Int, len: Int)
643       { send(conn, buf, start, len); }
644     override def close() { closeconn(conn, CF_CLOSEWR); }
645   }
646   lazy val output = new OutputStream;
647 }
648
649 /*----- Crypto-library hacks ----------------------------------------------*/
650
651 @native def hashsz(hash: String): Int;
652       /* Return the output hash size for the named HASH function, or -1. */
653
654 /*----- That's all, folks -------------------------------------------------*/
655
656 }