keys.scala, etc.: Make merging public keys have a progress bar.
[tripe-android] / sys.scala
CommitLineData
8eabb4ff
MW
1/* -*-scala-*-
2 *
25c35469 3 * System-level hacking
8eabb4ff
MW
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
c8292b34 30import scala.collection.convert.decorateAsJava._;
3bb2303d 31import scala.collection.mutable.{HashMap, HashSet};
8eabb4ff 32
c8292b34
MW
33import java.io.{BufferedReader, BufferedWriter, Closeable, File,
34 FileDescriptor, FileInputStream, FileOutputStream,
ad64fbfa 35 FileReader, FileWriter,
c8292b34
MW
36 InputStream, InputStreamReader,
37 OutputStream, OutputStreamWriter};
25c35469
MW
38import java.nio.{ByteBuffer, CharBuffer};
39import java.nio.charset.Charset;
40import java.util.Date;
8eabb4ff 41
0157de02
MW
42import Implicits.truish;
43
25c35469
MW
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
c8292b34 68 loop[CString] { exit =>
25c35469
MW
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;
c8292b34 96 exit(v);
25c35469
MW
97 }
98 }
25c35469
MW
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. */
3bb2303d 130System.loadLibrary("tripe");
25c35469 131
c8292b34
MW
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 */
25c35469 138type Wrapper = Array[Byte];
c8292b34 139class NativeObjectTypeException(msg: String) extends RuntimeException(msg);
8eabb4ff
MW
140
141/*----- Error codes -------------------------------------------------------*/
142
c8292b34 143/* Machinery for collecting error information from C. */
25c35469
MW
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
8eabb4ff 148object Errno extends Enumeration {
c8292b34
MW
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
8eabb4ff 171 val b = Map.newBuilder[String, Int];
25c35469 172 for (ErrorEntry(tag, err) <- errtab) b += tag -> err;
8eabb4ff
MW
173 b.result
174 }
8eabb4ff 175
c8292b34
MW
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
04a5abae
MW
181 class Val private[Errno](tag: String, val code: Int, id: Int)
182 extends super.Val(id, tag) {
c8292b34
MW
183 /* Our augmented error type. */
184
25c35469 185 def message: String = strerror(code).toJString;
8eabb4ff 186 }
c8292b34 187 private class UnknownError(code: Int)
04a5abae 188 extends Val("<unknown>", code, code);
8eabb4ff 189
04a5abae 190 private def err(tag: String, code: Int): Val = {
c8292b34
MW
191 /* Construct an error symbol given its tag string and a code number. */
192
04a5abae
MW
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) }
c8292b34 196 }
04a5abae 197 private def err(tag: String): Val =
c8292b34
MW
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)
8eabb4ff 203 }
8eabb4ff 204
c8292b34 205 val OK = err("OK", 0); // `errno' zero is a real thing
8eabb4ff
MW
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}
04a5abae 383import Errno.{Val => Errno, EEXIST, EISDIR, ENOENT, ENOTDIR};
8eabb4ff
MW
384
385object SystemError {
c8292b34
MW
386 /* Pattern matching for `SystemError', below. */
387
388 def apply(err: Errno, what: String): SystemError =
8eabb4ff 389 new SystemError(err, what);
c8292b34 390 def unapply(e: Exception): Option[(Errno, String)] = e match {
8eabb4ff
MW
391 case e: SystemError => Some((e.err, e.what))
392 case _ => None
393 }
394}
c8292b34
MW
395class SystemError (val err: Errno, val what: String) extends Exception {
396 /* An error from a syscall or similar, usually from native code. */
8eabb4ff 397
c8292b34 398 /* A constructor which takes an error number, for easier access from C. */
25c35469 399 private def this(err: Int, what: CString)
c8292b34
MW
400 { this(Errno.byid(err).asInstanceOf[Errno], what.toJString); }
401
8eabb4ff
MW
402 override def getMessage(): String = s"$what: ${err.message}";
403}
404
25c35469
MW
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); }
ad64fbfa
MW
413@native protected def chmod(path: CString, mode: Int);
414def chmod(path: String, mode: Int) { chmod(path.toCString, mode); }
25c35469
MW
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
c8292b34
MW
421@native def fdint(fd: FileDescriptor): Int;
422@native def newfd(fd: Int): FileDescriptor;
423@native def isatty(fd: FileDescriptor): Boolean;
424
25c35469
MW
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 */
c8292b34
MW
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. */
25c35469
MW
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 {
a5ec891a
MW
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;
25c35469
MW
451 type Type = Value;
452}
453import FileInfo._;
454
25c35469
MW
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) {
c8292b34
MW
463 /* Information about a file. This is constructed directly from native
464 * code.
465 */
466
25c35469
MW
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) {
c8292b34
MW
472 /* Lightly cook the values from the underlying `struct stat'. */
473
25c35469
MW
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
c8292b34 479 /* Return the file permissions only. */
25c35469
MW
480 def perms: Int = mode&0xfff;
481
c8292b34 482 /* Return the filetype, as a `FileInfo.Type'. */
25c35469
MW
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() {
c8292b34 495 /* Insist that you only ask for `rdev' fields on actual device nodes. */
25c35469 496 ftype match {
c8292b34 497 case CHR | BLK => ok;
25c35469
MW
498 case _ => throw new IllegalArgumentException("Object is not a device");
499 }
500 }
c8292b34
MW
501
502 /* Query the device-node numbers. */
25c35469
MW
503 def rdevMajor: Int = { mustBeDevice(); _rdevMajor }
504 def rdevMinor: Int = { mustBeDevice(); _rdevMinor }
505}
506
507/*----- Listing directories -----------------------------------------------*/
508
c8292b34 509/* Primitive operations. */
25c35469
MW
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 {
c8292b34
MW
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. */
25c35469
MW
525 def this(path: String) { this(path.toCString); }
526 def this(dir: File) { this(dir.getPath); }
c8292b34
MW
527
528 /* Cleaning up after ourselves. */
25c35469
MW
529 override def close() { closedir(cpath, dir); }
530 override protected def finalize() { super.finalize(); close(); }
c8292b34
MW
531
532 /* Subclass responsibility. */
25c35469 533 protected def mangle(file: String): T;
c8292b34
MW
534
535 /* Main machinery. */
536 private[this] val dir = opendir(cpath);
25c35469
MW
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) {
c8292b34
MW
547 /* Iterator over the basenames of files in a directory. */
548
25c35469 549 def this(dir: File) { this(dir.getPath); }
c8292b34 550
25c35469
MW
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) {
c8292b34
MW
556 /* Iterator over full `File' objects in a directory. */
557
25c35469
MW
558 def this(dir: File) { this(dir, dir.getPath.toCString); }
559 def this(path: String) { this(new File(path), path.toCString); }
c8292b34 560
25c35469
MW
561 override protected def mangle(file: String): File = new File(dir, file);
562}
563
564/*----- File locking ------------------------------------------------------*/
565
c8292b34
MW
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;
25c35469
MW
572@native protected def unlock(lock: Wrapper);
573
574class FileLock(path: String, flags: Int) extends Closeable {
c8292b34
MW
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 */
25c35469 580 def this(file: File, flags: Int) { this(file.getPath, flags); }
c8292b34
MW
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. */
25c35469 585 private[this] val lk = lock(path.toCString, flags);
c8292b34
MW
586
587 /* Making sure things get cleaned up. */
25c35469
MW
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) {
c8292b34
MW
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 */
25c35469 603
c8292b34
MW
604 /* Constructing names of files in a directory. Honestly, I'm surprised
605 * there isn't a method for this already.
606 */
04a5abae 607 def /(sub: String): File = new File(file, sub);
c8292b34
MW
608
609 /* Simple file operations. */
25c35469
MW
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); }
ad64fbfa 614 def chmod_!(mode: Int) { chmod(file.getPath, mode); }
25c35469
MW
615 def mkfile_!(mode: Int) { mkfile(file.getPath, mode); }
616 def mkfile_!() { mkfile_!(0x1b6); }
c8292b34 617 def rename_!(to: File) { rename(file.getPath, to.getPath); }
25c35469 618
c8292b34 619 /* Listing directories. */
25c35469
MW
620 def withFilesIterator[T](body: DirFilesIterator => T): T = {
621 val iter = new DirFilesIterator(file.getPath);
622 try { body(iter) } finally { iter.close(); }
623 }
c8292b34 624 def foreachFile(fn: File => Unit) { withFilesIterator(_.foreach(fn)) }
25c35469
MW
625 def files_! : Seq[File] = withFilesIterator { _.toSeq };
626
c8292b34 627 /* Low-level lFile information. */
25c35469
MW
628 def stat_! : FileInfo = stat(file.getPath);
629 def lstat_! : FileInfo = lstat(file.getPath);
630
c8292b34 631 /* Specific file-status queries. */
25c35469
MW
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
a5ec891a 649 /* Slightly more cooked file operations. */
25c35469 650 def remove_!() {
c8292b34 651 /* Delete a file, or directory, whatever it is. */
a5ec891a
MW
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; }
25c35469
MW
658 }
659 }
660
661 def rmTree() {
c8292b34 662 /* Delete a thing recursively. */
25c35469 663 def walk(f: File) {
c8292b34 664 if (f.isdir_!) f.foreachFile(walk _);
25c35469
MW
665 f.remove_!();
666 }
667 walk(file);
668 }
669
a5ec891a
MW
670 def mkdirNew_!() {
671 /* Make a directory if there's nothing there already. */
672 try { mkdir_!(); }
673 catch { case SystemError(EEXIST, _) => ok; }
674 }
675
c8292b34
MW
676 /* File locking. */
677 def lock_!(flags: Int): FileLock = new FileLock(file.getPath, flags);
678 def lock_!(): FileLock = lock_!(LKF_EXCL | 0x1b6);
25c35469 679 def withLock[T](flags: Int)(body: => T): T = {
c8292b34 680 val lk = lock_!(flags);
25c35469
MW
681 try { body } finally { lk.close(); }
682 }
c8292b34
MW
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);
ad64fbfa
MW
688 def reader(): BufferedReader = new BufferedReader(new FileReader(file));
689 def writer(): BufferedWriter = new BufferedWriter(new FileWriter(file));
c8292b34
MW
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 }
ad64fbfa
MW
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(); }
c8292b34 708 }
25c35469
MW
709 }
710}
711import FileImplicits._;
712
713/*----- Miscellaneous file hacks ------------------------------------------*/
8eabb4ff
MW
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
c8292b34 721 loop[File] { exit =>
8eabb4ff
MW
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 */
04a5abae 753 val f = d/b.result; b.clear();
c8292b34
MW
754 try { f.mkfile_!(); exit(f); }
755 catch { case SystemError(EEXIST, _) => ok; }
8eabb4ff 756 }
c8292b34
MW
757}
758
759/*----- Running a command -------------------------------------------------*/
8eabb4ff 760
c8292b34
MW
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. */
9190adc6 782 val pb = new ProcessBuilder(cmd.asJava);
c8292b34 783 val kid = pb.start(); clean { kid.destroy(); }
9190adc6 784 kid.getOutputStream.close();
c8292b34
MW
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;
0157de02 806 if (rc) throw new SubprocessFailed(cmd, rc, berr.result);
c8292b34
MW
807
808 /* We're all done. */
809 return (bout.result, berr.result);
810 }
8eabb4ff
MW
811}
812
04a5abae
MW
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
3bb2303d
MW
820@native protected def make_trigger(): Wrapper;
821@native protected def destroy_trigger(trig: Wrapper);
822@native protected def reset_trigger(trig: Wrapper);
04a5abae
MW
823@native protected def trigger(trig: Wrapper);
824
825private def getTrigger(): Wrapper = {
826 triggerLock synchronized {
0157de02 827 if (!nTriggers)
3bb2303d 828 make_trigger()
04a5abae
MW
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) {
3bb2303d 839 reset_trigger(trig);
04a5abae
MW
840 triggerLock synchronized {
841 if (nTriggers >= maxTriggers)
3bb2303d 842 destroy_trigger(trig);
04a5abae
MW
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
3bb2303d 869/*----- Glue for the VPN server -------------------------------------------*/
25c35469 870
3bb2303d
MW
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;
c8292b34 875
3bb2303d
MW
876/* Exceptions. */
877class NameResolutionException(msg: String) extends Exception(msg);
878class InitializationException(msg: String) extends Exception(msg);
25c35469 879
3bb2303d
MW
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}
c8292b34 911
3bb2303d
MW
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;
8eabb4ff 918 }
3bb2303d
MW
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}
25c35469 925
3bb2303d
MW
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() { }
8eabb4ff 932}
8eabb4ff 933
25c35469
MW
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. */
8eabb4ff
MW
938
939/*----- That's all, folks -------------------------------------------------*/
940
941}