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