keys.scala, etc.: Make merging public keys have a progress bar.
[tripe-android] / sys.scala
... / ...
CommitLineData
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
26package uk.org.distorted.tripe; package object sys {
27
28/*----- Imports -----------------------------------------------------------*/
29
30import scala.collection.convert.decorateAsJava._;
31import scala.collection.mutable.{HashMap, HashSet};
32
33import java.io.{BufferedReader, BufferedWriter, Closeable, File,
34 FileDescriptor, FileInputStream, FileOutputStream,
35 FileReader, FileWriter,
36 InputStream, InputStreamReader,
37 OutputStream, OutputStreamWriter};
38import java.nio.{ByteBuffer, CharBuffer};
39import java.nio.charset.Charset;
40import java.util.Date;
41
42import Implicits.truish;
43
44/*----- Some magic for C strings ------------------------------------------*/
45
46type 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
54class InvalidCStringException(msg: String) extends Exception(msg);
55
56object 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}
125import StringImplicits._;
126
127/*----- Main code ---------------------------------------------------------*/
128
129/* Import the native code library. */
130System.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 */
138type Wrapper = Array[Byte];
139class NativeObjectTypeException(msg: String) extends RuntimeException(msg);
140
141/*----- Error codes -------------------------------------------------------*/
142
143/* Machinery for collecting error information from C. */
144protected 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
148object 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}
383import Errno.{Val => Errno, EEXIST, EISDIR, ENOENT, ENOTDIR};
384
385object 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}
395class 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);
408def unlink(path: String) { unlink(path.toCString); }
409@native protected def rmdir(path: CString);
410def rmdir(path: String) { rmdir(path.toCString); }
411@native protected def mkdir(path: CString, mode: Int);
412def mkdir(path: String, mode: Int) { mkdir(path.toCString, mode); }
413@native protected def chmod(path: CString, mode: Int);
414def chmod(path: String, mode: Int) { chmod(path.toCString, mode); }
415@native protected def mkfile(path: CString, mode: Int);
416def mkfile(path: String, mode: Int) { mkfile(path.toCString, mode); }
417@native protected def rename(from: CString, to: CString);
418def 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 */
430final val S_IFMT = 0xf000;
431final val S_IFIFO = 0x1000;
432final val S_IFCHR = 0x2000;
433final val S_IFDIR = 0x4000;
434final val S_IFBLK = 0x6000;
435final val S_IFREG = 0x8000;
436final val S_IFLNK = 0xa000;
437final val S_IFSOCK = 0xc000;
438
439/* Primitive read-the-file-status calls. */
440@native protected def stat(path: CString): sys.FileInfo;
441def stat(path: String): sys.FileInfo = stat(path.toCString);
442@native protected def lstat(path: CString): sys.FileInfo;
443def lstat(path: String): sys.FileInfo = lstat(path.toCString);
444
445object 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}
453import FileInfo._;
454
455class 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
514protected 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
546class 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
554class 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 */
569final val LKF_EXCL = 0x1000;
570final val LKF_WAIT = 0x2000;
571@native protected def lock(path: CString, mode: Int): Wrapper;
572@native protected def unlock(lock: Wrapper);
573
574class 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
594object 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}
711import FileImplicits._;
712
713/*----- Miscellaneous file hacks ------------------------------------------*/
714
715def 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
761private val devnull = new File("/dev/null");
762
763private 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
770class 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
776def 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
815private val triggerLock = new Object;
816private final val maxTriggers = 2;
817private var nTriggers = 0;
818private 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
825private 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
838private 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
850private def withTrigger[T](body: Wrapper => T): T = {
851 val trig = getTrigger();
852 try { body(trig) }
853 finally { putTrigger(trig); }
854}
855
856def 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 */
874private class ServerLock;
875
876/* Exceptions. */
877class NameResolutionException(msg: String) extends Exception(msg);
878class 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
895base_init();
896setup_resolver();
897
898/* Tunnel descriptor plumbing. */
899val pending = HashMap[String, Int]();
900
901def getTunnelFd(peer: CString): Int =
902 pending synchronized { pending(peer.toJString) };
903def storeTunnelFd(peer: String, fd: Int)
904 { pending synchronized { pending(peer) = fd; } }
905def withdrawTunnelFd(peer: String)
906 { pending synchronized { pending -= peer; } }
907def 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. */
913lazy 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
926lazy 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}