5 * (c) 2018 Straylight/Edgeware
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of the Trivial IP Encryption (TrIPE) Android app.
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.
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
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/>.
26 package uk.org.distorted.tripe; package object sys {
28 /*----- Imports -----------------------------------------------------------*/
30 import scala.collection.convert.decorateAsJava._;
31 import scala.collection.mutable.{HashMap, HashSet};
33 import java.io.{BufferedReader, BufferedWriter, Closeable, File,
34 FileDescriptor, FileInputStream, FileOutputStream,
35 FileReader, FileWriter,
36 InputStream, InputStreamReader,
37 OutputStream, OutputStreamWriter};
38 import java.nio.{ByteBuffer, CharBuffer};
39 import java.nio.charset.Charset;
40 import java.util.Date;
42 import Implicits.truish;
44 /*----- Some magic for C strings ------------------------------------------*/
46 type CString = Array[Byte];
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.
54 class InvalidCStringException(msg: String) extends Exception(msg);
56 object StringImplicits {
57 implicit class ConvertJStringToCString(s: String) {
58 /* Magic to convert a string into a C string (null-terminated bytes). */
60 def toCString: CString = {
61 /* Convert the receiver to a C string. */
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);
68 loop[CString] { exit =>
69 /* If there's still stuff to encode, then encode it. Otherwise,
70 * there must be some dregs left in the encoder, so flush them out.
72 val r = if (in.hasRemaining) enc.encode(in, out, true)
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. */
81 val newout = ByteBuffer.allocate(sz);
82 out.flip(); newout.put(out);
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.
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);
102 implicit class ConvertCStringToJString(v: CString) {
103 /* Magic to convert a C string into a `proper' string. */
105 def toJString: String = {
106 /* Convert the receiver to a C string.
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
115 val inlen = v.indexOf(0) match {
119 val dec = Charset.defaultCharset.newDecoder;
120 val in = ByteBuffer.wrap(v, 0, inlen);
121 dec.decode(in).toString
125 import StringImplicits._;
127 /*----- Main code ---------------------------------------------------------*/
129 /* Import the native code library. */
130 System.loadLibrary("tripe");
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.
138 type Wrapper = Array[Byte];
139 class NativeObjectTypeException(msg: String) extends RuntimeException(msg);
141 /*----- Error codes -------------------------------------------------------*/
143 /* Machinery for collecting error information from C. */
144 protected case class ErrorEntry(val tag: String, val err: Int);
145 @native protected def errtab: Array[ErrorEntry];
146 @native protected def strerror(err: Int): CString;
148 object Errno extends Enumeration {
151 * There are two slight difficulties here.
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.
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.
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.
170 private val tagmap = { // map names to numbers based on what C reports
171 val b = Map.newBuilder[String, Int];
172 for (ErrorEntry(tag, err) <- errtab) b += tag -> err;
176 private val seen = HashSet[Int](); // which error codes have been taken
178 private var wrong = -256; // next synthetic code
179 private def nextwrong: Int = { val w = wrong; wrong -= 1; w }
181 class Val private[Errno](tag: String, val code: Int, id: Int)
182 extends super.Val(id, tag) {
183 /* Our augmented error type. */
185 def message: String = strerror(code).toJString;
187 private class UnknownError(code: Int)
188 extends Val("<unknown>", code, code);
190 private def err(tag: String, code: Int): Val = {
191 /* Construct an error symbol given its tag string and a code number. */
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) }
197 private def err(tag: String): Val =
198 err(tag, tagmap.getOrElse(tag, nextwrong));
200 def byid(id: Int): Value = {
201 if (seen contains id) apply(id)
202 else new UnknownError(id)
205 val OK = err("OK", 0); // `errno' zero is a real thing
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.
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
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)))
236 (goto-char (point-min))
237 (search-forward (concat "***" "BEGIN errtab" "***"))
238 (beginning-of-line 2)
239 (delete-region (point)
241 (search-forward "***END***")
245 (insert (format " val %s = err(\"%s\");\n" err err)))))
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");
383 import Errno.{Val => Errno, EEXIST, EISDIR, ENOENT, ENOTDIR};
386 /* Pattern matching for `SystemError', below. */
388 def apply(err: Errno, what: String): SystemError =
389 new SystemError(err, what);
390 def unapply(e: Exception): Option[(Errno, String)] = e match {
391 case e: SystemError => Some((e.err, e.what))
395 class SystemError (val err: Errno, val what: String) extends Exception {
396 /* An error from a syscall or similar, usually from native code. */
398 /* A constructor which takes an error number, for easier access from C. */
399 private def this(err: Int, what: CString)
400 { this(Errno.byid(err).asInstanceOf[Errno], what.toJString); }
402 override def getMessage(): String = s"$what: ${err.message}";
405 /*----- Basic file operations ---------------------------------------------*/
407 @native protected def unlink(path: CString);
408 def unlink(path: String) { unlink(path.toCString); }
409 @native protected def rmdir(path: CString);
410 def rmdir(path: String) { rmdir(path.toCString); }
411 @native protected def mkdir(path: CString, mode: Int);
412 def mkdir(path: String, mode: Int) { mkdir(path.toCString, mode); }
413 @native protected def chmod(path: CString, mode: Int);
414 def chmod(path: String, mode: Int) { chmod(path.toCString, mode); }
415 @native protected def mkfile(path: CString, mode: Int);
416 def mkfile(path: String, mode: Int) { mkfile(path.toCString, mode); }
417 @native protected def rename(from: CString, to: CString);
418 def rename(from: String, to: String)
419 { rename(from.toCString, to.toCString); }
421 @native def fdint(fd: FileDescriptor): Int;
422 @native def newfd(fd: Int): FileDescriptor;
423 @native def isatty(fd: FileDescriptor): Boolean;
425 /*----- File status information -------------------------------------------*/
427 /* These are the traditional values, but the C code carefully arranges to
428 * return them regardless of what your kernel actually thinks.
430 final val S_IFMT = 0xf000;
431 final val S_IFIFO = 0x1000;
432 final val S_IFCHR = 0x2000;
433 final val S_IFDIR = 0x4000;
434 final val S_IFBLK = 0x6000;
435 final val S_IFREG = 0x8000;
436 final val S_IFLNK = 0xa000;
437 final val S_IFSOCK = 0xc000;
439 /* Primitive read-the-file-status calls. */
440 @native protected def stat(path: CString): sys.FileInfo;
441 def stat(path: String): sys.FileInfo = stat(path.toCString);
442 @native protected def lstat(path: CString): sys.FileInfo;
443 def lstat(path: String): sys.FileInfo = lstat(path.toCString);
445 object FileInfo extends Enumeration {
446 /* A simple enumeration of things a file might be.
448 * `HDLNK' is a hard link, used in `tar' files.
450 val FIFO, CHR, DIR, BLK, REG, LNK, SOCK, HDLNK, UNK = Value;
455 class 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,
460 val blksize: Int, val blocks: Long,
461 val atime: Date, val mtime: Date,
463 /* Information about a file. This is constructed directly from native
467 private def this(devMajor: Int, devMinor: Int, ino: Long,
468 mode: Int, nlink: Int, uid: Int, gid: Int,
469 rdevMinor: Int, rdevMajor: Int,
470 size: Long, blksize: Int, blocks: Long,
471 atime: Long, mtime: Long, ctime: Long) {
472 /* Lightly cook the values from the underlying `struct stat'. */
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));
479 /* Return the file permissions only. */
480 def perms: Int = mode&0xfff;
482 /* Return the filetype, as a `FileInfo.Type'. */
483 def ftype: Type = (mode&S_IFMT) match {
490 case S_IFSOCK => SOCK
494 private[this] def mustBeDevice() {
495 /* Insist that you only ask for `rdev' fields on actual device nodes. */
497 case CHR | BLK => ok;
498 case _ => throw new IllegalArgumentException("Object is not a device");
502 /* Query the device-node numbers. */
503 def rdevMajor: Int = { mustBeDevice(); _rdevMajor }
504 def rdevMinor: Int = { mustBeDevice(); _rdevMinor }
507 /*----- Listing directories -----------------------------------------------*/
509 /* Primitive operations. */
510 @native protected def opendir(path: CString): Wrapper;
511 @native protected def readdir(path: CString, dir: Wrapper): CString;
512 @native protected def closedir(path: CString, dir: Wrapper);
514 protected abstract class BaseDirIterator[T](cpath: CString)
515 extends LookaheadIterator[T] with Closeable {
516 /* The underlying machinery for directory iterators.
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.
524 /* Constructors from more convenient types. */
525 def this(path: String) { this(path.toCString); }
526 def this(dir: File) { this(dir.getPath); }
528 /* Cleaning up after ourselves. */
529 override def close() { closedir(cpath, dir); }
530 override protected def finalize() { super.finalize(); close(); }
532 /* Subclass responsibility. */
533 protected def mangle(file: String): T;
535 /* Main machinery. */
536 private[this] val dir = opendir(cpath);
537 override protected def fetch(): Option[T] = readdir(cpath, dir) match {
539 case f => f.toJString match {
540 case "." | ".." => fetch()
541 case jf => Some(mangle(jf))
546 class DirIterator(val path: String) extends BaseDirIterator[String](path) {
547 /* Iterator over the basenames of files in a directory. */
549 def this(dir: File) { this(dir.getPath); }
551 override protected def mangle(file: String): String = file;
554 class DirFilesIterator private[this](val dir: File, cpath: CString)
555 extends BaseDirIterator[File](cpath) {
556 /* Iterator over full `File' objects in a directory. */
558 def this(dir: File) { this(dir, dir.getPath.toCString); }
559 def this(path: String) { this(new File(path), path.toCString); }
561 override protected def mangle(file: String): File = new File(dir, file);
564 /*----- File locking ------------------------------------------------------*/
566 /* Primitive operations. The low `mode' bits are for the lock file if we
569 final val LKF_EXCL = 0x1000;
570 final val LKF_WAIT = 0x2000;
571 @native protected def lock(path: CString, mode: Int): Wrapper;
572 @native protected def unlock(lock: Wrapper);
574 class FileLock(path: String, flags: Int) extends Closeable {
575 /* A class which represents a held lock on a file. */
577 /* Constructors. The default is to take an exclusive lock or fail
580 def this(file: File, flags: Int) { this(file.getPath, flags); }
581 def this(path: String) { this(path, LKF_EXCL | 0x1b6); }
582 def this(file: File) { this(file.getPath, LKF_EXCL | 0x1b6); }
584 /* The low-level lock object, actually a file descriptor. */
585 private[this] val lk = lock(path.toCString, flags);
587 /* Making sure things get cleaned up. */
588 override def close() { unlock(lk); }
589 override protected def finalize() { super.finalize(); close(); }
592 /*----- File implicits ----------------------------------------------------*/
594 object FileImplicits {
595 implicit class FileOps(file: File) {
596 /* Augment `File' with operations which throw informative (if low-level
597 * and system-specific) exceptions rather than returning unhelpful
598 * win/lose booleans. These have names ending with `_!' because they
601 * And some other useful methods.
604 /* Constructing names of files in a directory. Honestly, I'm surprised
605 * there isn't a method for this already.
607 def /(sub: String): File = new File(file, sub);
609 /* Simple file operations. */
610 def unlink_!() { unlink(file.getPath); }
611 def rmdir_!() { rmdir(file.getPath); }
612 def mkdir_!(mode: Int) { mkdir(file.getPath, mode); }
613 def mkdir_!() { mkdir_!(0x1ff); }
614 def chmod_!(mode: Int) { chmod(file.getPath, mode); }
615 def mkfile_!(mode: Int) { mkfile(file.getPath, mode); }
616 def mkfile_!() { mkfile_!(0x1b6); }
617 def rename_!(to: File) { rename(file.getPath, to.getPath); }
619 /* Listing directories. */
620 def withFilesIterator[T](body: DirFilesIterator => T): T = {
621 val iter = new DirFilesIterator(file.getPath);
622 try { body(iter) } finally { iter.close(); }
624 def foreachFile(fn: File => Unit) { withFilesIterator(_.foreach(fn)) }
625 def files_! : Seq[File] = withFilesIterator { _.toSeq };
627 /* Low-level lFile information. */
628 def stat_! : FileInfo = stat(file.getPath);
629 def lstat_! : FileInfo = lstat(file.getPath);
631 /* Specific file-status queries. */
632 private[this] def statish[T](statfn: String => FileInfo,
633 ifexists: FileInfo => T,
634 ifmissing: => T): T =
635 (try { statfn(file.getPath) }
636 catch { case SystemError(ENOENT, _) => null }) match {
637 case null => ifmissing
638 case st => ifexists(st)
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);
649 /* Slightly more cooked file operations. */
651 /* Delete a file, or directory, whatever it is. */
652 try { unlink_!(); return; }
654 case SystemError(ENOENT, _) => return;
655 case SystemError(EISDIR, _) =>
656 try { rmdir_!(); return; }
657 catch { case SystemError(ENOENT, _) => return; }
662 /* Delete a thing recursively. */
664 if (f.isdir_!) f.foreachFile(walk _);
671 /* Make a directory if there's nothing there already. */
673 catch { case SystemError(EEXIST, _) => ok; }
677 def lock_!(flags: Int): FileLock = new FileLock(file.getPath, flags);
678 def lock_!(): FileLock = lock_!(LKF_EXCL | 0x1b6);
679 def withLock[T](flags: Int)(body: => T): T = {
680 val lk = lock_!(flags);
681 try { body } finally { lk.close(); }
683 def withLock[T](body: => T): T = withLock(LKF_EXCL | 0x1b6) { body };
685 /* Opening files. Again, I'm surprised this isn't here already. */
686 def open(): FileInputStream = new FileInputStream(file);
687 def openForOutput(): FileOutputStream = new FileOutputStream(file);
688 def reader(): BufferedReader = new BufferedReader(new FileReader(file));
689 def writer(): BufferedWriter = new BufferedWriter(new FileWriter(file));
690 def withInput[T](body: FileInputStream => T): T = {
693 finally { in.close(); }
695 def withOutput[T](body: FileOutputStream => T): T = {
696 val out = openForOutput();
697 try { body(out) } finally { out.close(); }
699 def withReader[T](body: BufferedReader => T): T = {
702 finally { r.close(); }
704 def withWriter[T](body: BufferedWriter => T): T = {
707 finally { w.close(); }
711 import FileImplicits._;
713 /*----- Miscellaneous file hacks ------------------------------------------*/
715 def freshFile(d: File): File = {
716 /* Return the name of a freshly created file in directory D. */
718 val buf = new Array[Byte](6);
719 val b = new StringBuilder;
722 /* Keep going until we find a fresh one. */
724 /* Provide a prefix. Mostly this is to prevent the file starting with
725 * an unfortunate character like `-'.
729 /* Generate some random bytes. */
732 /* Now turn the bytes into a filename. This is a cheesy implementation
733 * of Base64 encoding.
739 a = (a << 8) | x; n += 8;
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) '+'
750 /* Make the filename, and try to create the file. If we succeed, we
753 val f = d/b.result; b.clear();
754 try { f.mkfile_!(); exit(f); }
755 catch { case SystemError(EEXIST, _) => ok; }
759 /*----- Running a command -------------------------------------------------*/
761 private val devnull = new File("/dev/null");
763 private def captureStream(in: InputStream, out: StringBuilder) {
764 /* Capture the INSTREAM's contents in a string. */
766 for ((buf, n) <- blocks(new InputStreamReader(in)))
767 out.appendAll(buf, 0, n);
770 class SubprocessFailed(val cmd: Seq[String], rc: Int, stderr: String)
772 override def getMessage(): String =
773 s"process (${quoteTokens(cmd)}) failed (rc = $rc):\n" + stderr
776 def runCommand(cmd: String*): (String, String) = {
777 /* Run a command, returning its stdout and stderr. */
779 withCleaner { clean =>
781 /* Create the child process and pick up the ends of its streams. */
782 val pb = new ProcessBuilder(cmd.asJava);
783 val kid = pb.start(); clean { kid.destroy(); }
784 kid.getOutputStream.close();
785 val out = kid.getInputStream(); clean { out.close(); }
786 val err = kid.getErrorStream(); clean { err.close(); }
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.
792 val bout, berr = new StringBuilder;
793 val rdout = thread("capture process stdout", daemon = false) {
794 captureStream(out, bout);
796 val rderr = thread("capture process stderr", daemon = false) {
797 captureStream(err, berr);
799 val wait = thread("await process exit", daemon = false) {
802 rdout.join(); rderr.join(); wait.join();
804 /* Check the exit status. */
805 val rc = kid.exitValue;
806 if (rc) throw new SubprocessFailed(cmd, rc, berr.result);
808 /* We're all done. */
809 return (bout.result, berr.result);
813 /*----- Interrupt triggers ------------------------------------------------*/
815 private val triggerLock = new Object;
816 private final val maxTriggers = 2;
817 private var nTriggers = 0;
818 private var triggers: List[Wrapper] = Nil;
820 @native protected def make_trigger(): Wrapper;
821 @native protected def destroy_trigger(trig: Wrapper);
822 @native protected def reset_trigger(trig: Wrapper);
823 @native protected def trigger(trig: Wrapper);
825 private def getTrigger(): Wrapper = {
826 triggerLock synchronized {
830 val trig = triggers.head;
831 triggers = triggers.tail;
838 private def putTrigger(trig: Wrapper) {
840 triggerLock synchronized {
841 if (nTriggers >= maxTriggers)
842 destroy_trigger(trig);
850 private def withTrigger[T](body: Wrapper => T): T = {
851 val trig = getTrigger();
853 finally { putTrigger(trig); }
856 def interruptWithTrigger[T](body: Wrapper => T): T = {
857 /* interruptWithTrigger { TRIG => BODY }
859 * Execute BODY and return its result. If the thread receives an
860 * interrupt, the trigger TRIG will be pulled. See `interruptably' for the
864 withTrigger { trig =>
865 interruptably { body(trig) } onInterrupt { trigger(trig); }
869 /*----- Glue for the VPN server -------------------------------------------*/
871 /* The lock class. This is only a class because they're much easier to find
872 * than loose objects through JNI.
874 private class ServerLock;
877 class NameResolutionException(msg: String) extends Exception(msg);
878 class InitializationException(msg: String) extends Exception(msg);
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);
890 @native protected def send(buf: CString, start: Int, len: Int,
892 @native protected def recv(buf: CString, start: Int, len: Int,
898 /* Tunnel descriptor plumbing. */
899 val pending = HashMap[String, Int]();
901 def getTunnelFd(peer: CString): Int =
902 pending synchronized { pending(peer.toJString) };
903 def storeTunnelFd(peer: String, fd: Int)
904 { pending synchronized { pending(peer) = fd; } }
905 def withdrawTunnelFd(peer: String)
906 { pending synchronized { pending -= peer; } }
907 def withTunnelFd[T](peer: String, fd: Int)(body: => T): T = {
908 storeTunnelFd(peer, fd);
909 try { body } finally { withdrawTunnelFd(peer); }
913 lazy 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;
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() { }
926 lazy 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() { }
934 /*----- Crypto-library hacks ----------------------------------------------*/
936 @native def hashsz(hash: String): Int;
937 /* Return the output hash size for the named HASH function, or -1. */
939 /*----- That's all, folks -------------------------------------------------*/