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