More work in progress.
[tripe-android] / sys.scala
1 /* -*-scala-*-
2 *
3 * System-level hacking
4 *
5 * (c) 2018 Straylight/Edgeware
6 */
7
8 /*----- Licensing notice --------------------------------------------------*
9 *
10 * This file is part of the Trivial IP Encryption (TrIPE) Android app.
11 *
12 * TrIPE is free software: you can redistribute it and/or modify it under
13 * the terms of the GNU General Public License as published by the Free
14 * Software Foundation; either version 3 of the License, or (at your
15 * option) any later version.
16 *
17 * TrIPE is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
20 * for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with TrIPE. If not, see <https://www.gnu.org/licenses/>.
24 */
25
26 package uk.org.distorted.tripe; package object sys {
27
28 /*----- Imports -----------------------------------------------------------*/
29
30 import scala.collection.convert.decorateAsJava._;
31 import scala.collection.mutable.HashSet;
32
33 import java.io.{BufferedReader, BufferedWriter, Closeable, File,
34 FileDescriptor, FileInputStream, FileOutputStream,
35 InputStream, InputStreamReader,
36 OutputStream, OutputStreamWriter};
37 import java.nio.{ByteBuffer, CharBuffer};
38 import java.nio.charset.Charset;
39 import java.util.Date;
40
41 /*----- Some magic for C strings ------------------------------------------*/
42
43 type 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
51 class InvalidCStringException(msg: String) extends Exception(msg);
52
53 object 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
65 loop[CString] { exit =>
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;
93 exit(v);
94 }
95 }
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 }
122 import StringImplicits._;
123
124 /*----- Main code ---------------------------------------------------------*/
125
126 /* Import the native code library. */
127 System.loadLibrary("toy");
128
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 */
135 type Wrapper = Array[Byte];
136 class NativeObjectTypeException(msg: String) extends RuntimeException(msg);
137
138 /*----- Error codes -------------------------------------------------------*/
139
140 /* Machinery for collecting error information from C. */
141 protected 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
145 object Errno extends Enumeration {
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
168 val b = Map.newBuilder[String, Int];
169 for (ErrorEntry(tag, err) <- errtab) b += tag -> err;
170 b.result
171 }
172
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
178 class Val private[Errno](tag: String, val code: Int, id: Int)
179 extends super.Val(id, tag) {
180 /* Our augmented error type. */
181
182 def message: String = strerror(code).toJString;
183 }
184 private class UnknownError(code: Int)
185 extends Val("<unknown>", code, code);
186
187 private def err(tag: String, code: Int): Val = {
188 /* Construct an error symbol given its tag string and a code number. */
189
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) }
193 }
194 private def err(tag: String): Val =
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)
200 }
201
202 val OK = err("OK", 0); // `errno' zero is a real thing
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 }
380 import Errno.{Val => Errno, EEXIST, EISDIR, ENOENT, ENOTDIR};
381
382 object SystemError {
383 /* Pattern matching for `SystemError', below. */
384
385 def apply(err: Errno, what: String): SystemError =
386 new SystemError(err, what);
387 def unapply(e: Exception): Option[(Errno, String)] = e match {
388 case e: SystemError => Some((e.err, e.what))
389 case _ => None
390 }
391 }
392 class SystemError (val err: Errno, val what: String) extends Exception {
393 /* An error from a syscall or similar, usually from native code. */
394
395 /* A constructor which takes an error number, for easier access from C. */
396 private def this(err: Int, what: CString)
397 { this(Errno.byid(err).asInstanceOf[Errno], what.toJString); }
398
399 override def getMessage(): String = s"$what: ${err.message}";
400 }
401
402 /*----- Basic file operations ---------------------------------------------*/
403
404 @native protected def unlink(path: CString);
405 def unlink(path: String) { unlink(path.toCString); }
406 @native protected def rmdir(path: CString);
407 def rmdir(path: String) { rmdir(path.toCString); }
408 @native protected def mkdir(path: CString, mode: Int);
409 def mkdir(path: String, mode: Int) { mkdir(path.toCString, mode); }
410 @native protected def mkfile(path: CString, mode: Int);
411 def mkfile(path: String, mode: Int) { mkfile(path.toCString, mode); }
412 @native protected def rename(from: CString, to: CString);
413 def rename(from: String, to: String)
414 { rename(from.toCString, to.toCString); }
415
416 @native def fdint(fd: FileDescriptor): Int;
417 @native def newfd(fd: Int): FileDescriptor;
418 @native def isatty(fd: FileDescriptor): Boolean;
419
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 */
425 final val S_IFMT = 0xf000;
426 final val S_IFIFO = 0x1000;
427 final val S_IFCHR = 0x2000;
428 final val S_IFDIR = 0x4000;
429 final val S_IFBLK = 0x6000;
430 final val S_IFREG = 0x8000;
431 final val S_IFLNK = 0xa000;
432 final val S_IFSOCK = 0xc000;
433
434 /* Primitive read-the-file-status calls. */
435 @native protected def stat(path: CString): sys.FileInfo;
436 def stat(path: String): sys.FileInfo = stat(path.toCString);
437 @native protected def lstat(path: CString): sys.FileInfo;
438 def lstat(path: String): sys.FileInfo = lstat(path.toCString);
439
440 object FileInfo extends Enumeration {
441 /* A simple enumeration of things a file might be. */
442 val FIFO, CHR, DIR, BLK, REG, LNK, SOCK, UNK = Value;
443 type Type = Value;
444 }
445 import FileInfo._;
446
447 class FileInfo private[this](val devMajor: Int, val devMinor: Int,
448 val ino: Long, val mode: Int, val nlink: Int,
449 val uid: Int, val gid: Int,
450 _rdevMinor: Int, _rdevMajor: Int,
451 val size: Long,
452 val blksize: Int, val blocks: Long,
453 val atime: Date, val mtime: Date,
454 val ctime: Date) {
455 /* Information about a file. This is constructed directly from native
456 * code.
457 */
458
459 private def this(devMajor: Int, devMinor: Int, ino: Long,
460 mode: Int, nlink: Int, uid: Int, gid: Int,
461 rdevMinor: Int, rdevMajor: Int,
462 size: Long, blksize: Int, blocks: Long,
463 atime: Long, mtime: Long, ctime: Long) {
464 /* Lightly cook the values from the underlying `struct stat'. */
465
466 this(devMajor, devMinor, ino, mode, nlink, uid, gid,
467 rdevMajor, rdevMinor, size, blksize, blocks,
468 new Date(atime), new Date(mtime), new Date(ctime));
469 }
470
471 /* Return the file permissions only. */
472 def perms: Int = mode&0xfff;
473
474 /* Return the filetype, as a `FileInfo.Type'. */
475 def ftype: Type = (mode&S_IFMT) match {
476 case S_IFIFO => FIFO
477 case S_IFCHR => CHR
478 case S_IFDIR => DIR
479 case S_IFBLK => BLK
480 case S_IFREG => REG
481 case S_IFLNK => LNK
482 case S_IFSOCK => SOCK
483 case _ => UNK
484 }
485
486 private[this] def mustBeDevice() {
487 /* Insist that you only ask for `rdev' fields on actual device nodes. */
488 ftype match {
489 case CHR | BLK => ok;
490 case _ => throw new IllegalArgumentException("Object is not a device");
491 }
492 }
493
494 /* Query the device-node numbers. */
495 def rdevMajor: Int = { mustBeDevice(); _rdevMajor }
496 def rdevMinor: Int = { mustBeDevice(); _rdevMinor }
497 }
498
499 /*----- Listing directories -----------------------------------------------*/
500
501 /* Primitive operations. */
502 @native protected def opendir(path: CString): Wrapper;
503 @native protected def readdir(path: CString, dir: Wrapper): CString;
504 @native protected def closedir(path: CString, dir: Wrapper);
505
506 protected abstract class BaseDirIterator[T](cpath: CString)
507 extends LookaheadIterator[T] with Closeable {
508 /* The underlying machinery for directory iterators.
509 *
510 * Subclasses must define `mangle' to convert raw filenames into a T.
511 * We keep track of the path C-string, because we need to keep passing that
512 * back to C for inclusion in error messages. Recording higher-level
513 * things is left for subclasses.
514 */
515
516 /* Constructors from more convenient types. */
517 def this(path: String) { this(path.toCString); }
518 def this(dir: File) { this(dir.getPath); }
519
520 /* Cleaning up after ourselves. */
521 override def close() { closedir(cpath, dir); }
522 override protected def finalize() { super.finalize(); close(); }
523
524 /* Subclass responsibility. */
525 protected def mangle(file: String): T;
526
527 /* Main machinery. */
528 private[this] val dir = opendir(cpath);
529 override protected def fetch(): Option[T] = readdir(cpath, dir) match {
530 case null => None
531 case f => f.toJString match {
532 case "." | ".." => fetch()
533 case jf => Some(mangle(jf))
534 }
535 }
536 }
537
538 class DirIterator(val path: String) extends BaseDirIterator[String](path) {
539 /* Iterator over the basenames of files in a directory. */
540
541 def this(dir: File) { this(dir.getPath); }
542
543 override protected def mangle(file: String): String = file;
544 }
545
546 class DirFilesIterator private[this](val dir: File, cpath: CString)
547 extends BaseDirIterator[File](cpath) {
548 /* Iterator over full `File' objects in a directory. */
549
550 def this(dir: File) { this(dir, dir.getPath.toCString); }
551 def this(path: String) { this(new File(path), path.toCString); }
552
553 override protected def mangle(file: String): File = new File(dir, file);
554 }
555
556 /*----- File locking ------------------------------------------------------*/
557
558 /* Primitive operations. The low `mode' bits are for the lock file if we
559 * have to create it.
560 */
561 final val LKF_EXCL = 0x1000;
562 final val LKF_WAIT = 0x2000;
563 @native protected def lock(path: CString, mode: Int): Wrapper;
564 @native protected def unlock(lock: Wrapper);
565
566 class FileLock(path: String, flags: Int) extends Closeable {
567 /* A class which represents a held lock on a file. */
568
569 /* Constructors. The default is to take an exclusive lock or fail
570 * immediately.
571 */
572 def this(file: File, flags: Int) { this(file.getPath, flags); }
573 def this(path: String) { this(path, LKF_EXCL | 0x1b6); }
574 def this(file: File) { this(file.getPath, LKF_EXCL | 0x1b6); }
575
576 /* The low-level lock object, actually a file descriptor. */
577 private[this] val lk = lock(path.toCString, flags);
578
579 /* Making sure things get cleaned up. */
580 override def close() { unlock(lk); }
581 override protected def finalize() { super.finalize(); close(); }
582 }
583
584 /*----- File implicits ----------------------------------------------------*/
585
586 object FileImplicits {
587 implicit class FileOps(file: File) {
588 /* Augment `File' with operations which throw informative (if low-level
589 * and system-specific) exceptions rather than returning unhelpful
590 * win/lose booleans. These have names ending with `_!' because they
591 * might explode.
592 *
593 * And some other useful methods.
594 */
595
596 /* Constructing names of files in a directory. Honestly, I'm surprised
597 * there isn't a method for this already.
598 */
599 def /(sub: String): File = new File(file, sub);
600
601 /* Simple file operations. */
602 def unlink_!() { unlink(file.getPath); }
603 def rmdir_!() { rmdir(file.getPath); }
604 def mkdir_!(mode: Int) { mkdir(file.getPath, mode); }
605 def mkdir_!() { mkdir_!(0x1ff); }
606 def mkfile_!(mode: Int) { mkfile(file.getPath, mode); }
607 def mkfile_!() { mkfile_!(0x1b6); }
608 def rename_!(to: File) { rename(file.getPath, to.getPath); }
609
610 /* Listing directories. */
611 def withFilesIterator[T](body: DirFilesIterator => T): T = {
612 val iter = new DirFilesIterator(file.getPath);
613 try { body(iter) } finally { iter.close(); }
614 }
615 def foreachFile(fn: File => Unit) { withFilesIterator(_.foreach(fn)) }
616 def files_! : Seq[File] = withFilesIterator { _.toSeq };
617
618 /* Low-level lFile information. */
619 def stat_! : FileInfo = stat(file.getPath);
620 def lstat_! : FileInfo = lstat(file.getPath);
621
622 /* Specific file-status queries. */
623 private[this] def statish[T](statfn: String => FileInfo,
624 ifexists: FileInfo => T,
625 ifmissing: => T): T =
626 (try { statfn(file.getPath) }
627 catch { case SystemError(ENOENT, _) => null }) match {
628 case null => ifmissing
629 case st => ifexists(st)
630 };
631 def exists_! : Boolean = statish(stat _, _ => true, false);
632 def isfifo_! : Boolean = statish(stat _, _.ftype == FIFO, false);
633 def isblk_! : Boolean = statish(stat _, _.ftype == BLK, false);
634 def isdir_! : Boolean = statish(stat _, _.ftype == DIR, false);
635 def ischr_! : Boolean = statish(stat _, _.ftype == CHR, false);
636 def isreg_! : Boolean = statish(stat _, _.ftype == REG, false);
637 def islnk_! : Boolean = statish(lstat _, _.ftype == LNK, false);
638 def issock_! : Boolean = statish(stat _, _.ftype == SOCK, false);
639
640 def remove_!() {
641 /* Delete a file, or directory, whatever it is. */
642 while (true) {
643 try { unlink_!(); return; }
644 catch {
645 case SystemError(ENOENT, _) => return;
646 case SystemError(EISDIR, _) => ok;
647 }
648 try { rmdir_!(); return; }
649 catch {
650 case SystemError(ENOENT, _) => return;
651 case SystemError(ENOTDIR, _) => ok;
652 }
653 }
654 }
655
656 def rmTree() {
657 /* Delete a thing recursively. */
658 def walk(f: File) {
659 if (f.isdir_!) f.foreachFile(walk _);
660 f.remove_!();
661 }
662 walk(file);
663 }
664
665 /* File locking. */
666 def lock_!(flags: Int): FileLock = new FileLock(file.getPath, flags);
667 def lock_!(): FileLock = lock_!(LKF_EXCL | 0x1b6);
668 def withLock[T](flags: Int)(body: => T): T = {
669 val lk = lock_!(flags);
670 try { body } finally { lk.close(); }
671 }
672 def withLock[T](body: => T): T = withLock(LKF_EXCL | 0x1b6) { body };
673
674 /* Opening files. Again, I'm surprised this isn't here already. */
675 def open(): FileInputStream = new FileInputStream(file);
676 def openForOutput(): FileOutputStream = new FileOutputStream(file);
677 def reader(): BufferedReader =
678 new BufferedReader(new InputStreamReader(open()));
679 def writer(): BufferedWriter =
680 new BufferedWriter(new OutputStreamWriter(openForOutput()));
681 def withInput[T](body: FileInputStream => T): T = {
682 val in = open();
683 try { body(in) }
684 finally { in.close(); }
685 }
686 def withOutput[T](body: FileOutputStream => T): T = {
687 val out = openForOutput();
688 try { body(out) } finally { out.close(); }
689 }
690 def withReader[T](body: BufferedReader => T): T = withInput { in =>
691 body(new BufferedReader(new InputStreamReader(in)))
692 };
693 def withWriter[T](body: BufferedWriter => T): T = withOutput { out =>
694 val w = new BufferedWriter(new OutputStreamWriter(out));
695 /* Do this the hard way, so that we flush the `BufferedWriter'. */
696 try { body(w) } finally { w.close(); }
697 }
698 }
699 }
700 import FileImplicits._;
701
702 /*----- Miscellaneous file hacks ------------------------------------------*/
703
704 def freshFile(d: File): File = {
705 /* Return the name of a freshly created file in directory D. */
706
707 val buf = new Array[Byte](6);
708 val b = new StringBuilder;
709
710 loop[File] { exit =>
711 /* Keep going until we find a fresh one. */
712
713 /* Provide a prefix. Mostly this is to prevent the file starting with
714 * an unfortunate character like `-'.
715 */
716 b ++= "tmp.";
717
718 /* Generate some random bytes. */
719 rng.nextBytes(buf);
720
721 /* Now turn the bytes into a filename. This is a cheesy implementation
722 * of Base64 encoding.
723 */
724 var a = 0;
725 var n = 0;
726
727 for (x <- buf) {
728 a = (a << 8) | x; n += 8;
729 while (n >= 6) {
730 val y = (a >> n - 6)&0x3f; n -= 6;
731 b += (if (y < 26) 'A' + y
732 else if (y < 52) 'a' + (y - 26)
733 else if (y < 62) '0' + (y - 52)
734 else if (y == 62) '+'
735 else '-').toChar;
736 }
737 }
738
739 /* Make the filename, and try to create the file. If we succeed, we
740 * win.
741 */
742 val f = d/b.result; b.clear();
743 try { f.mkfile_!(); exit(f); }
744 catch { case SystemError(EEXIST, _) => ok; }
745 }
746 }
747
748 /*----- Running a command -------------------------------------------------*/
749
750 private val devnull = new File("/dev/null");
751
752 private def captureStream(in: InputStream, out: StringBuilder) {
753 /* Capture the INSTREAM's contents in a string. */
754
755 for ((buf, n) <- blocks(new InputStreamReader(in)))
756 out.appendAll(buf, 0, n);
757 }
758
759 class SubprocessFailed(val cmd: Seq[String], rc: Int, stderr: String)
760 extends Exception {
761 override def getMessage(): String =
762 s"process (${quoteTokens(cmd)}) failed (rc = $rc):\n" + stderr
763 }
764
765 def runCommand(cmd: String*): (String, String) = {
766 /* Run a command, returning its stdout and stderr. */
767
768 withCleaner { clean =>
769
770 /* Create the child process and pick up the ends of its streams. */
771 val pb = new ProcessBuilder(cmd.asJava).redirectInput(devnull);
772 val kid = pb.start(); clean { kid.destroy(); }
773 val out = kid.getInputStream(); clean { out.close(); }
774 val err = kid.getErrorStream(); clean { err.close(); }
775
776 /* Capture the output in threads, so we don't block. Also, wait for the
777 * child to complete. Amazingly, messing with threads here isn't too
778 * much of a disaster.
779 */
780 val bout, berr = new StringBuilder;
781 val rdout = thread("capture process stdout", daemon = false) {
782 captureStream(out, bout);
783 }
784 val rderr = thread("capture process stderr", daemon = false) {
785 captureStream(err, berr);
786 }
787 val wait = thread("await process exit", daemon = false) {
788 kid.waitFor();
789 }
790 rdout.join(); rderr.join(); wait.join();
791
792 /* Check the exit status. */
793 val rc = kid.exitValue;
794 if (rc != 0) throw new SubprocessFailed(cmd, rc, berr.result);
795
796 /* We're all done. */
797 return (bout.result, berr.result);
798 }
799 }
800
801 /*----- Interrupt triggers ------------------------------------------------*/
802
803 private val triggerLock = new Object;
804 private final val maxTriggers = 2;
805 private var nTriggers = 0;
806 private var triggers: List[Wrapper] = Nil;
807
808 @native protected def makeTrigger(): Wrapper;
809 @native protected def destroyTrigger(trig: Wrapper);
810 @native protected def resetTrigger(trig: Wrapper);
811 @native protected def trigger(trig: Wrapper);
812
813 private def getTrigger(): Wrapper = {
814 triggerLock synchronized {
815 if (nTriggers == 0)
816 makeTrigger()
817 else {
818 val trig = triggers.head;
819 triggers = triggers.tail;
820 nTriggers -= 1;
821 trig
822 }
823 }
824 }
825
826 private def putTrigger(trig: Wrapper) {
827 resetTrigger(trig);
828 triggerLock synchronized {
829 if (nTriggers >= maxTriggers)
830 destroyTrigger(trig);
831 else {
832 triggers ::= trig;
833 nTriggers += 1;
834 }
835 }
836 }
837
838 private def withTrigger[T](body: Wrapper => T): T = {
839 val trig = getTrigger();
840 try { body(trig) }
841 finally { putTrigger(trig); }
842 }
843
844 def interruptWithTrigger[T](body: Wrapper => T): T = {
845 /* interruptWithTrigger { TRIG => BODY }
846 *
847 * Execute BODY and return its result. If the thread receives an
848 * interrupt, the trigger TRIG will be pulled. See `interruptably' for the
849 * full semantics.
850 */
851
852 withTrigger { trig =>
853 interruptably { body(trig) } onInterrupt { trigger(trig); }
854 };
855 }
856
857 /*----- Connecting to a server --------------------------------------------*/
858
859 /* Primitive operations. */
860 final val CF_CLOSERD = 1;
861 final val CF_CLOSEWR = 2;
862 final val CF_CLOSEMASK = CF_CLOSERD | CF_CLOSEWR;
863 @native protected def connect(path: CString, trig: Wrapper): Wrapper;
864 @native protected def send(conn: Wrapper, buf: CString,
865 start: Int, len: Int, trig: Wrapper);
866 @native protected def recv(conn: Wrapper, buf: CString,
867 start: Int, len: Int, trig: Wrapper): Int;
868 @native def closeconn(conn: Wrapper, how: Int);
869
870 class Connection(path: String) extends Closeable {
871
872 /* The underlying primitive connection. */
873 private[this] val conn = interruptWithTrigger { trig =>
874 connect(path.toCString, trig);
875 };
876
877 /* Alternative constructors. */
878 def this(file: File) { this(file.getPath); }
879
880 /* Cleanup.*/
881 override def close() { closeconn(conn, CF_CLOSEMASK); }
882 override protected def finalize() { super.finalize(); close(); }
883
884 class Input private[Connection] extends InputStream {
885 /* An input stream which reads from the connection. */
886
887 override def read(): Int = {
888 val buf = new Array[Byte](1);
889 val n = read(buf, 0, 1);
890 if (n < 0) -1 else buf(0)&0xff;
891 }
892 override def read(buf: Array[Byte]): Int =
893 read(buf, 0, buf.length);
894 override def read(buf: Array[Byte], start: Int, len: Int) =
895 interruptWithTrigger { trig => recv(conn, buf, start, len, trig); };
896 override def close() { closeconn(conn, CF_CLOSERD); }
897 }
898 lazy val input = new Input;
899
900 class Output private[Connection] extends OutputStream {
901 /* An output stream which writes to the connection. */
902
903 override def write(b: Int) { write(Array[Byte](b.toByte), 0, 1); }
904 override def write(buf: Array[Byte]) { write(buf, 0, buf.length); }
905 override def write(buf: Array[Byte], start: Int, len: Int)
906 { interruptWithTrigger { trig => send(conn, buf, start, len, trig); } }
907 override def close() { closeconn(conn, CF_CLOSEWR); }
908 }
909 lazy val output = new Output;
910 }
911
912 /*----- Crypto-library hacks ----------------------------------------------*/
913
914 @native def hashsz(hash: String): Int;
915 /* Return the output hash size for the named HASH function, or -1. */
916
917 /*----- That's all, folks -------------------------------------------------*/
918
919 }