--- /dev/null
+/* -*-scala-*-
+ *
+ * System calls and errors
+ *
+ * (c) 2018 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the Trivial IP Encryption (TrIPE) Android app.
+ *
+ * TrIPE is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * TrIPE is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TrIPE. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package uk.org.distorted.tripe; package object sys {
+
+/*----- Imports -----------------------------------------------------------*/
+
+import scala.collection.mutable.HashSet;
+
+import java.io.File;
+
+import Magic._;
+
+/*----- Error codes -------------------------------------------------------*/
+
+object Errno extends Enumeration {
+ private[this] val tagmap = {
+ val b = Map.newBuilder[String, Int];
+ for (jni.ErrorEntry(tag, err) <- jni.errtab) b += tag -> err;
+ b.result
+ }
+ private[this] var wrong = -255;
+ private[this] val seen = HashSet[Int]();
+
+ class ErrnoVal private[Errno](tag: String, val code: Int, id: Int)
+ extends Val(id, tag) {
+ def message: String = jni.strerror(code).toJString;
+ }
+
+ private[this] def err(tag: String, code: Int): ErrnoVal = {
+ if (seen contains code) { wrong -= 1; new ErrnoVal(tag, code, wrong) }
+ else { seen += code; new ErrnoVal(tag, code, code) }
+ }
+ private[this] def err(tag: String): ErrnoVal = err(tag, tagmap(tag));
+
+ val OK = err("OK", 0);
+
+ /*
+ ;;; The errno name table is very boring to type. To make life less
+ ;;; awful, put the errno names in this list and evaluate the code to
+ ;;; get Emacs to regenerate it.
+
+ (let ((errors '(EPERM ENOENT ESRCH EINTR EIO ENXIO E2BIG ENOEXEC EBADF
+ ECHILD EAGAIN ENOMEM EACCES EFAULT ENOTBLK EBUSY EEXIST
+ EXDEV ENODEV ENOTDIR EISDIR EINVAL ENFILE EMFILE ENOTTY
+ ETXTBSY EFBIG ENOSPC ESPIPE EROFS EMLINK EPIPE EDOM
+ ERANGE
+
+ EDEADLK ENAMETOOLONG ENOLCK ENOSYS ENOTEMPTY ELOOP
+ EWOULDBLOCK ENOMSG EIDRM ECHRNG EL2NSYNC EL3HLT EL3RST
+ ELNRNG EUNATCH ENOCSI EL2HLT EBADE EBADR EXFULL ENOANO
+ EBADRQC EBADSLT EDEADLOCK EBFONT ENOSTR ENODATA ETIME
+ ENOSR ENONET ENOPKG EREMOTE ENOLINK EADV ESRMNT ECOMM
+ EPROTO EMULTIHOP EDOTDOT EBADMSG EOVERFLOW ENOTUNIQ
+ EBADFD EREMCHG ELIBACC ELIBBAD ELIBSCN ELIBMAX ELIBEXEC
+ EILSEQ ERESTART ESTRPIPE EUSERS ENOTSOCK EDESTADDRREQ
+ EMSGSIZE EPROTOTYPE ENOPROTOOPT EPROTONOSUPPORT
+ ESOCKTNOSUPPORT EOPNOTSUPP EPFNOSUPPORT EAFNOSUPPORT
+ EADDRINUSE EADDRNOTAVAIL ENETDOWN ENETUNREACH ENETRESET
+ ECONNABORTED ECONNRESET ENOBUFS EISCONN ENOTCONN
+ ESHUTDOWN ETOOMANYREFS ETIMEDOUT ECONNREFUSED EHOSTDOWN
+ EHOSTUNREACH EALREADY EINPROGRESS ESTALE EUCLEAN ENOTNAM
+ ENAVAIL EISNAM EREMOTEIO EDQUOT ENOMEDIUM EMEDIUMTYPE
+ ECANCELED ENOKEY EKEYEXPIRED EKEYREVOKED EKEYREJECTED
+ EOWNERDEAD ENOTRECOVERABLE ERFKILL EHWPOISON)))
+ (save-excursion
+ (goto-char (point-min))
+ (search-forward (concat "***" "BEGIN errtab" "***"))
+ (beginning-of-line 2)
+ (delete-region (point)
+ (progn
+ (search-forward "***END***")
+ (beginning-of-line)
+ (point)))
+ (dolist (err errors)
+ (insert (format " val %s = err(\"%s\");\n" err err)))))
+ */
+ /***BEGIN errtab***/
+ val EPERM = err("EPERM");
+ val ENOENT = err("ENOENT");
+ val ESRCH = err("ESRCH");
+ val EINTR = err("EINTR");
+ val EIO = err("EIO");
+ val ENXIO = err("ENXIO");
+ val E2BIG = err("E2BIG");
+ val ENOEXEC = err("ENOEXEC");
+ val EBADF = err("EBADF");
+ val ECHILD = err("ECHILD");
+ val EAGAIN = err("EAGAIN");
+ val ENOMEM = err("ENOMEM");
+ val EACCES = err("EACCES");
+ val EFAULT = err("EFAULT");
+ val ENOTBLK = err("ENOTBLK");
+ val EBUSY = err("EBUSY");
+ val EEXIST = err("EEXIST");
+ val EXDEV = err("EXDEV");
+ val ENODEV = err("ENODEV");
+ val ENOTDIR = err("ENOTDIR");
+ val EISDIR = err("EISDIR");
+ val EINVAL = err("EINVAL");
+ val ENFILE = err("ENFILE");
+ val EMFILE = err("EMFILE");
+ val ENOTTY = err("ENOTTY");
+ val ETXTBSY = err("ETXTBSY");
+ val EFBIG = err("EFBIG");
+ val ENOSPC = err("ENOSPC");
+ val ESPIPE = err("ESPIPE");
+ val EROFS = err("EROFS");
+ val EMLINK = err("EMLINK");
+ val EPIPE = err("EPIPE");
+ val EDOM = err("EDOM");
+ val ERANGE = err("ERANGE");
+ val EDEADLK = err("EDEADLK");
+ val ENAMETOOLONG = err("ENAMETOOLONG");
+ val ENOLCK = err("ENOLCK");
+ val ENOSYS = err("ENOSYS");
+ val ENOTEMPTY = err("ENOTEMPTY");
+ val ELOOP = err("ELOOP");
+ val EWOULDBLOCK = err("EWOULDBLOCK");
+ val ENOMSG = err("ENOMSG");
+ val EIDRM = err("EIDRM");
+ val ECHRNG = err("ECHRNG");
+ val EL2NSYNC = err("EL2NSYNC");
+ val EL3HLT = err("EL3HLT");
+ val EL3RST = err("EL3RST");
+ val ELNRNG = err("ELNRNG");
+ val EUNATCH = err("EUNATCH");
+ val ENOCSI = err("ENOCSI");
+ val EL2HLT = err("EL2HLT");
+ val EBADE = err("EBADE");
+ val EBADR = err("EBADR");
+ val EXFULL = err("EXFULL");
+ val ENOANO = err("ENOANO");
+ val EBADRQC = err("EBADRQC");
+ val EBADSLT = err("EBADSLT");
+ val EDEADLOCK = err("EDEADLOCK");
+ val EBFONT = err("EBFONT");
+ val ENOSTR = err("ENOSTR");
+ val ENODATA = err("ENODATA");
+ val ETIME = err("ETIME");
+ val ENOSR = err("ENOSR");
+ val ENONET = err("ENONET");
+ val ENOPKG = err("ENOPKG");
+ val EREMOTE = err("EREMOTE");
+ val ENOLINK = err("ENOLINK");
+ val EADV = err("EADV");
+ val ESRMNT = err("ESRMNT");
+ val ECOMM = err("ECOMM");
+ val EPROTO = err("EPROTO");
+ val EMULTIHOP = err("EMULTIHOP");
+ val EDOTDOT = err("EDOTDOT");
+ val EBADMSG = err("EBADMSG");
+ val EOVERFLOW = err("EOVERFLOW");
+ val ENOTUNIQ = err("ENOTUNIQ");
+ val EBADFD = err("EBADFD");
+ val EREMCHG = err("EREMCHG");
+ val ELIBACC = err("ELIBACC");
+ val ELIBBAD = err("ELIBBAD");
+ val ELIBSCN = err("ELIBSCN");
+ val ELIBMAX = err("ELIBMAX");
+ val ELIBEXEC = err("ELIBEXEC");
+ val EILSEQ = err("EILSEQ");
+ val ERESTART = err("ERESTART");
+ val ESTRPIPE = err("ESTRPIPE");
+ val EUSERS = err("EUSERS");
+ val ENOTSOCK = err("ENOTSOCK");
+ val EDESTADDRREQ = err("EDESTADDRREQ");
+ val EMSGSIZE = err("EMSGSIZE");
+ val EPROTOTYPE = err("EPROTOTYPE");
+ val ENOPROTOOPT = err("ENOPROTOOPT");
+ val EPROTONOSUPPORT = err("EPROTONOSUPPORT");
+ val ESOCKTNOSUPPORT = err("ESOCKTNOSUPPORT");
+ val EOPNOTSUPP = err("EOPNOTSUPP");
+ val EPFNOSUPPORT = err("EPFNOSUPPORT");
+ val EAFNOSUPPORT = err("EAFNOSUPPORT");
+ val EADDRINUSE = err("EADDRINUSE");
+ val EADDRNOTAVAIL = err("EADDRNOTAVAIL");
+ val ENETDOWN = err("ENETDOWN");
+ val ENETUNREACH = err("ENETUNREACH");
+ val ENETRESET = err("ENETRESET");
+ val ECONNABORTED = err("ECONNABORTED");
+ val ECONNRESET = err("ECONNRESET");
+ val ENOBUFS = err("ENOBUFS");
+ val EISCONN = err("EISCONN");
+ val ENOTCONN = err("ENOTCONN");
+ val ESHUTDOWN = err("ESHUTDOWN");
+ val ETOOMANYREFS = err("ETOOMANYREFS");
+ val ETIMEDOUT = err("ETIMEDOUT");
+ val ECONNREFUSED = err("ECONNREFUSED");
+ val EHOSTDOWN = err("EHOSTDOWN");
+ val EHOSTUNREACH = err("EHOSTUNREACH");
+ val EALREADY = err("EALREADY");
+ val EINPROGRESS = err("EINPROGRESS");
+ val ESTALE = err("ESTALE");
+ val EUCLEAN = err("EUCLEAN");
+ val ENOTNAM = err("ENOTNAM");
+ val ENAVAIL = err("ENAVAIL");
+ val EISNAM = err("EISNAM");
+ val EREMOTEIO = err("EREMOTEIO");
+ val EDQUOT = err("EDQUOT");
+ val ENOMEDIUM = err("ENOMEDIUM");
+ val EMEDIUMTYPE = err("EMEDIUMTYPE");
+ val ECANCELED = err("ECANCELED");
+ val ENOKEY = err("ENOKEY");
+ val EKEYEXPIRED = err("EKEYEXPIRED");
+ val EKEYREVOKED = err("EKEYREVOKED");
+ val EKEYREJECTED = err("EKEYREJECTED");
+ val EOWNERDEAD = err("EOWNERDEAD");
+ val ENOTRECOVERABLE = err("ENOTRECOVERABLE");
+ val ERFKILL = err("ERFKILL");
+ val EHWPOISON = err("EHWPOISON");
+ /***end***/
+}
+import Errno.{Value => _, _};
+
+object SystemError {
+ def apply(err: Errno.Value, what: String): SystemError =
+ new SystemError(err, what);
+ def unapply(e: Exception): Option[(Errno.Value, String)] = e match {
+ case e: SystemError => Some((e.err, e.what))
+ case _ => None
+ }
+}
+
+class SystemError private[this](val err: Errno.ErrnoVal, val what: String)
+ extends Exception {
+ def this(err: Errno.Value, what: String)
+ { this(err.asInstanceOf[Errno.ErrnoVal], what); }
+ private[tripe] def this(err: Int, what: CString)
+ { this(Errno(err), what.toJString); }
+ override def getMessage(): String = s"$what: ${err.message}";
+}
+
+/*----- Filesystem hacks --------------------------------------------------*/
+
+def freshFile(d: File): File = {
+ /* Return the name of a freshly created file in directory D. */
+
+ val buf = new Array[Byte](6);
+ val b = new StringBuilder;
+
+ while (true) {
+ /* Keep going until we find a fresh one. */
+
+ /* Provide a prefix. Mostly this is to prevent the file starting with
+ * an unfortunate character like `-'.
+ */
+ b ++= "tmp.";
+
+ /* Generate some random bytes. */
+ rng.nextBytes(buf);
+
+ /* Now turn the bytes into a filename. This is a cheesy implementation
+ * of Base64 encoding.
+ */
+ var a = 0;
+ var n = 0;
+
+ for (x <- buf) {
+ a = (a << 8) | x; n += 8;
+ while (n >= 6) {
+ val y = (a >> n - 6)&0x3f; n -= 6;
+ b += (if (y < 26) 'A' + y
+ else if (y < 52) 'a' + (y - 26)
+ else if (y < 62) '0' + (y - 52)
+ else if (y == 62) '+'
+ else '-').toChar;
+ }
+ }
+
+ /* Make the filename, and try to create the file. If we succeed, we
+ * win.
+ */
+ val f = new File(d, b.result); b.clear();
+ try { jni.mkfile(f); return f; }
+ catch { case SystemError(EEXIST, _) => (); }
+ }
+
+ /* We shouldn't get here, but the type checker needs placating. */
+ unreachable("unreachable");
+}
+
+def rmTree(f: File) {
+ def walk(f: File) {
+ if (jni.stat(f).isdir) {
+ closing(new jni.DirFilesIterator(f)) { _ foreach(walk _) }
+ try { jni.rmdir(f); }
+ catch { case SystemError(ENOENT, _) => (); }
+ } else {
+ try { jni.unlink(f); }
+ catch { case SystemError(ENOENT, _) => (); }
+ }
+ }
+ walk(f);
+}
+def rmTree(path: String) { rmTree(new File(path)); }
+
+def fileExists(path: String): Boolean =
+ try { jni.stat(path); true }
+ catch { case SystemError(ENOENT, _) => false };
+def fileExists(file: File): Boolean = fileExists(file.getPath);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+}