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