| 1 | /* -*-scala-*- |
| 2 | * |
| 3 | * System calls and errors |
| 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.mutable.HashSet; |
| 31 | |
| 32 | import java.io.File; |
| 33 | |
| 34 | import Magic._; |
| 35 | |
| 36 | /*----- Error codes -------------------------------------------------------*/ |
| 37 | |
| 38 | object Errno extends Enumeration { |
| 39 | private[this] val tagmap = { |
| 40 | val b = Map.newBuilder[String, Int]; |
| 41 | for (jni.ErrorEntry(tag, err) <- jni.errtab) b += tag -> err; |
| 42 | b.result |
| 43 | } |
| 44 | private[this] var wrong = -255; |
| 45 | private[this] val seen = HashSet[Int](); |
| 46 | |
| 47 | class ErrnoVal private[Errno](tag: String, val code: Int, id: Int) |
| 48 | extends Val(id, tag) { |
| 49 | def message: String = jni.strerror(code).toJString; |
| 50 | } |
| 51 | |
| 52 | private[this] def err(tag: String, code: Int): ErrnoVal = { |
| 53 | if (seen contains code) { wrong -= 1; new ErrnoVal(tag, code, wrong) } |
| 54 | else { seen += code; new ErrnoVal(tag, code, code) } |
| 55 | } |
| 56 | private[this] def err(tag: String): ErrnoVal = err(tag, tagmap(tag)); |
| 57 | |
| 58 | val OK = err("OK", 0); |
| 59 | |
| 60 | /* |
| 61 | ;;; The errno name table is very boring to type. To make life less |
| 62 | ;;; awful, put the errno names in this list and evaluate the code to |
| 63 | ;;; get Emacs to regenerate it. |
| 64 | |
| 65 | (let ((errors '(EPERM ENOENT ESRCH EINTR EIO ENXIO E2BIG ENOEXEC EBADF |
| 66 | ECHILD EAGAIN ENOMEM EACCES EFAULT ENOTBLK EBUSY EEXIST |
| 67 | EXDEV ENODEV ENOTDIR EISDIR EINVAL ENFILE EMFILE ENOTTY |
| 68 | ETXTBSY EFBIG ENOSPC ESPIPE EROFS EMLINK EPIPE EDOM |
| 69 | ERANGE |
| 70 | |
| 71 | EDEADLK ENAMETOOLONG ENOLCK ENOSYS ENOTEMPTY ELOOP |
| 72 | EWOULDBLOCK ENOMSG EIDRM ECHRNG EL2NSYNC EL3HLT EL3RST |
| 73 | ELNRNG EUNATCH ENOCSI EL2HLT EBADE EBADR EXFULL ENOANO |
| 74 | EBADRQC EBADSLT EDEADLOCK EBFONT ENOSTR ENODATA ETIME |
| 75 | ENOSR ENONET ENOPKG EREMOTE ENOLINK EADV ESRMNT ECOMM |
| 76 | EPROTO EMULTIHOP EDOTDOT EBADMSG EOVERFLOW ENOTUNIQ |
| 77 | EBADFD EREMCHG ELIBACC ELIBBAD ELIBSCN ELIBMAX ELIBEXEC |
| 78 | EILSEQ ERESTART ESTRPIPE EUSERS ENOTSOCK EDESTADDRREQ |
| 79 | EMSGSIZE EPROTOTYPE ENOPROTOOPT EPROTONOSUPPORT |
| 80 | ESOCKTNOSUPPORT EOPNOTSUPP EPFNOSUPPORT EAFNOSUPPORT |
| 81 | EADDRINUSE EADDRNOTAVAIL ENETDOWN ENETUNREACH ENETRESET |
| 82 | ECONNABORTED ECONNRESET ENOBUFS EISCONN ENOTCONN |
| 83 | ESHUTDOWN ETOOMANYREFS ETIMEDOUT ECONNREFUSED EHOSTDOWN |
| 84 | EHOSTUNREACH EALREADY EINPROGRESS ESTALE EUCLEAN ENOTNAM |
| 85 | ENAVAIL EISNAM EREMOTEIO EDQUOT ENOMEDIUM EMEDIUMTYPE |
| 86 | ECANCELED ENOKEY EKEYEXPIRED EKEYREVOKED EKEYREJECTED |
| 87 | EOWNERDEAD ENOTRECOVERABLE ERFKILL EHWPOISON))) |
| 88 | (save-excursion |
| 89 | (goto-char (point-min)) |
| 90 | (search-forward (concat "***" "BEGIN errtab" "***")) |
| 91 | (beginning-of-line 2) |
| 92 | (delete-region (point) |
| 93 | (progn |
| 94 | (search-forward "***END***") |
| 95 | (beginning-of-line) |
| 96 | (point))) |
| 97 | (dolist (err errors) |
| 98 | (insert (format " val %s = err(\"%s\");\n" err err))))) |
| 99 | */ |
| 100 | /***BEGIN errtab***/ |
| 101 | val EPERM = err("EPERM"); |
| 102 | val ENOENT = err("ENOENT"); |
| 103 | val ESRCH = err("ESRCH"); |
| 104 | val EINTR = err("EINTR"); |
| 105 | val EIO = err("EIO"); |
| 106 | val ENXIO = err("ENXIO"); |
| 107 | val E2BIG = err("E2BIG"); |
| 108 | val ENOEXEC = err("ENOEXEC"); |
| 109 | val EBADF = err("EBADF"); |
| 110 | val ECHILD = err("ECHILD"); |
| 111 | val EAGAIN = err("EAGAIN"); |
| 112 | val ENOMEM = err("ENOMEM"); |
| 113 | val EACCES = err("EACCES"); |
| 114 | val EFAULT = err("EFAULT"); |
| 115 | val ENOTBLK = err("ENOTBLK"); |
| 116 | val EBUSY = err("EBUSY"); |
| 117 | val EEXIST = err("EEXIST"); |
| 118 | val EXDEV = err("EXDEV"); |
| 119 | val ENODEV = err("ENODEV"); |
| 120 | val ENOTDIR = err("ENOTDIR"); |
| 121 | val EISDIR = err("EISDIR"); |
| 122 | val EINVAL = err("EINVAL"); |
| 123 | val ENFILE = err("ENFILE"); |
| 124 | val EMFILE = err("EMFILE"); |
| 125 | val ENOTTY = err("ENOTTY"); |
| 126 | val ETXTBSY = err("ETXTBSY"); |
| 127 | val EFBIG = err("EFBIG"); |
| 128 | val ENOSPC = err("ENOSPC"); |
| 129 | val ESPIPE = err("ESPIPE"); |
| 130 | val EROFS = err("EROFS"); |
| 131 | val EMLINK = err("EMLINK"); |
| 132 | val EPIPE = err("EPIPE"); |
| 133 | val EDOM = err("EDOM"); |
| 134 | val ERANGE = err("ERANGE"); |
| 135 | val EDEADLK = err("EDEADLK"); |
| 136 | val ENAMETOOLONG = err("ENAMETOOLONG"); |
| 137 | val ENOLCK = err("ENOLCK"); |
| 138 | val ENOSYS = err("ENOSYS"); |
| 139 | val ENOTEMPTY = err("ENOTEMPTY"); |
| 140 | val ELOOP = err("ELOOP"); |
| 141 | val EWOULDBLOCK = err("EWOULDBLOCK"); |
| 142 | val ENOMSG = err("ENOMSG"); |
| 143 | val EIDRM = err("EIDRM"); |
| 144 | val ECHRNG = err("ECHRNG"); |
| 145 | val EL2NSYNC = err("EL2NSYNC"); |
| 146 | val EL3HLT = err("EL3HLT"); |
| 147 | val EL3RST = err("EL3RST"); |
| 148 | val ELNRNG = err("ELNRNG"); |
| 149 | val EUNATCH = err("EUNATCH"); |
| 150 | val ENOCSI = err("ENOCSI"); |
| 151 | val EL2HLT = err("EL2HLT"); |
| 152 | val EBADE = err("EBADE"); |
| 153 | val EBADR = err("EBADR"); |
| 154 | val EXFULL = err("EXFULL"); |
| 155 | val ENOANO = err("ENOANO"); |
| 156 | val EBADRQC = err("EBADRQC"); |
| 157 | val EBADSLT = err("EBADSLT"); |
| 158 | val EDEADLOCK = err("EDEADLOCK"); |
| 159 | val EBFONT = err("EBFONT"); |
| 160 | val ENOSTR = err("ENOSTR"); |
| 161 | val ENODATA = err("ENODATA"); |
| 162 | val ETIME = err("ETIME"); |
| 163 | val ENOSR = err("ENOSR"); |
| 164 | val ENONET = err("ENONET"); |
| 165 | val ENOPKG = err("ENOPKG"); |
| 166 | val EREMOTE = err("EREMOTE"); |
| 167 | val ENOLINK = err("ENOLINK"); |
| 168 | val EADV = err("EADV"); |
| 169 | val ESRMNT = err("ESRMNT"); |
| 170 | val ECOMM = err("ECOMM"); |
| 171 | val EPROTO = err("EPROTO"); |
| 172 | val EMULTIHOP = err("EMULTIHOP"); |
| 173 | val EDOTDOT = err("EDOTDOT"); |
| 174 | val EBADMSG = err("EBADMSG"); |
| 175 | val EOVERFLOW = err("EOVERFLOW"); |
| 176 | val ENOTUNIQ = err("ENOTUNIQ"); |
| 177 | val EBADFD = err("EBADFD"); |
| 178 | val EREMCHG = err("EREMCHG"); |
| 179 | val ELIBACC = err("ELIBACC"); |
| 180 | val ELIBBAD = err("ELIBBAD"); |
| 181 | val ELIBSCN = err("ELIBSCN"); |
| 182 | val ELIBMAX = err("ELIBMAX"); |
| 183 | val ELIBEXEC = err("ELIBEXEC"); |
| 184 | val EILSEQ = err("EILSEQ"); |
| 185 | val ERESTART = err("ERESTART"); |
| 186 | val ESTRPIPE = err("ESTRPIPE"); |
| 187 | val EUSERS = err("EUSERS"); |
| 188 | val ENOTSOCK = err("ENOTSOCK"); |
| 189 | val EDESTADDRREQ = err("EDESTADDRREQ"); |
| 190 | val EMSGSIZE = err("EMSGSIZE"); |
| 191 | val EPROTOTYPE = err("EPROTOTYPE"); |
| 192 | val ENOPROTOOPT = err("ENOPROTOOPT"); |
| 193 | val EPROTONOSUPPORT = err("EPROTONOSUPPORT"); |
| 194 | val ESOCKTNOSUPPORT = err("ESOCKTNOSUPPORT"); |
| 195 | val EOPNOTSUPP = err("EOPNOTSUPP"); |
| 196 | val EPFNOSUPPORT = err("EPFNOSUPPORT"); |
| 197 | val EAFNOSUPPORT = err("EAFNOSUPPORT"); |
| 198 | val EADDRINUSE = err("EADDRINUSE"); |
| 199 | val EADDRNOTAVAIL = err("EADDRNOTAVAIL"); |
| 200 | val ENETDOWN = err("ENETDOWN"); |
| 201 | val ENETUNREACH = err("ENETUNREACH"); |
| 202 | val ENETRESET = err("ENETRESET"); |
| 203 | val ECONNABORTED = err("ECONNABORTED"); |
| 204 | val ECONNRESET = err("ECONNRESET"); |
| 205 | val ENOBUFS = err("ENOBUFS"); |
| 206 | val EISCONN = err("EISCONN"); |
| 207 | val ENOTCONN = err("ENOTCONN"); |
| 208 | val ESHUTDOWN = err("ESHUTDOWN"); |
| 209 | val ETOOMANYREFS = err("ETOOMANYREFS"); |
| 210 | val ETIMEDOUT = err("ETIMEDOUT"); |
| 211 | val ECONNREFUSED = err("ECONNREFUSED"); |
| 212 | val EHOSTDOWN = err("EHOSTDOWN"); |
| 213 | val EHOSTUNREACH = err("EHOSTUNREACH"); |
| 214 | val EALREADY = err("EALREADY"); |
| 215 | val EINPROGRESS = err("EINPROGRESS"); |
| 216 | val ESTALE = err("ESTALE"); |
| 217 | val EUCLEAN = err("EUCLEAN"); |
| 218 | val ENOTNAM = err("ENOTNAM"); |
| 219 | val ENAVAIL = err("ENAVAIL"); |
| 220 | val EISNAM = err("EISNAM"); |
| 221 | val EREMOTEIO = err("EREMOTEIO"); |
| 222 | val EDQUOT = err("EDQUOT"); |
| 223 | val ENOMEDIUM = err("ENOMEDIUM"); |
| 224 | val EMEDIUMTYPE = err("EMEDIUMTYPE"); |
| 225 | val ECANCELED = err("ECANCELED"); |
| 226 | val ENOKEY = err("ENOKEY"); |
| 227 | val EKEYEXPIRED = err("EKEYEXPIRED"); |
| 228 | val EKEYREVOKED = err("EKEYREVOKED"); |
| 229 | val EKEYREJECTED = err("EKEYREJECTED"); |
| 230 | val EOWNERDEAD = err("EOWNERDEAD"); |
| 231 | val ENOTRECOVERABLE = err("ENOTRECOVERABLE"); |
| 232 | val ERFKILL = err("ERFKILL"); |
| 233 | val EHWPOISON = err("EHWPOISON"); |
| 234 | /***end***/ |
| 235 | } |
| 236 | import Errno.{Value => _, _}; |
| 237 | |
| 238 | object SystemError { |
| 239 | def apply(err: Errno.Value, what: String): SystemError = |
| 240 | new SystemError(err, what); |
| 241 | def unapply(e: Exception): Option[(Errno.Value, String)] = e match { |
| 242 | case e: SystemError => Some((e.err, e.what)) |
| 243 | case _ => None |
| 244 | } |
| 245 | } |
| 246 | |
| 247 | class SystemError private[this](val err: Errno.ErrnoVal, val what: String) |
| 248 | extends Exception { |
| 249 | def this(err: Errno.Value, what: String) |
| 250 | { this(err.asInstanceOf[Errno.ErrnoVal], what); } |
| 251 | private[tripe] def this(err: Int, what: CString) |
| 252 | { this(Errno(err), what.toJString); } |
| 253 | override def getMessage(): String = s"$what: ${err.message}"; |
| 254 | } |
| 255 | |
| 256 | /*----- Filesystem hacks --------------------------------------------------*/ |
| 257 | |
| 258 | def freshFile(d: File): File = { |
| 259 | /* Return the name of a freshly created file in directory D. */ |
| 260 | |
| 261 | val buf = new Array[Byte](6); |
| 262 | val b = new StringBuilder; |
| 263 | |
| 264 | while (true) { |
| 265 | /* Keep going until we find a fresh one. */ |
| 266 | |
| 267 | /* Provide a prefix. Mostly this is to prevent the file starting with |
| 268 | * an unfortunate character like `-'. |
| 269 | */ |
| 270 | b ++= "tmp."; |
| 271 | |
| 272 | /* Generate some random bytes. */ |
| 273 | rng.nextBytes(buf); |
| 274 | |
| 275 | /* Now turn the bytes into a filename. This is a cheesy implementation |
| 276 | * of Base64 encoding. |
| 277 | */ |
| 278 | var a = 0; |
| 279 | var n = 0; |
| 280 | |
| 281 | for (x <- buf) { |
| 282 | a = (a << 8) | x; n += 8; |
| 283 | while (n >= 6) { |
| 284 | val y = (a >> n - 6)&0x3f; n -= 6; |
| 285 | b += (if (y < 26) 'A' + y |
| 286 | else if (y < 52) 'a' + (y - 26) |
| 287 | else if (y < 62) '0' + (y - 52) |
| 288 | else if (y == 62) '+' |
| 289 | else '-').toChar; |
| 290 | } |
| 291 | } |
| 292 | |
| 293 | /* Make the filename, and try to create the file. If we succeed, we |
| 294 | * win. |
| 295 | */ |
| 296 | val f = new File(d, b.result); b.clear(); |
| 297 | try { jni.mkfile(f); return f; } |
| 298 | catch { case SystemError(EEXIST, _) => (); } |
| 299 | } |
| 300 | |
| 301 | /* We shouldn't get here, but the type checker needs placating. */ |
| 302 | unreachable("unreachable"); |
| 303 | } |
| 304 | |
| 305 | def rmTree(f: File) { |
| 306 | def walk(f: File) { |
| 307 | if (jni.stat(f).isdir) { |
| 308 | closing(new jni.DirFilesIterator(f)) { _ foreach(walk _) } |
| 309 | try { jni.rmdir(f); } |
| 310 | catch { case SystemError(ENOENT, _) => (); } |
| 311 | } else { |
| 312 | try { jni.unlink(f); } |
| 313 | catch { case SystemError(ENOENT, _) => (); } |
| 314 | } |
| 315 | } |
| 316 | walk(f); |
| 317 | } |
| 318 | def rmTree(path: String) { rmTree(new File(path)); } |
| 319 | |
| 320 | def fileExists(path: String): Boolean = |
| 321 | try { jni.stat(path); true } |
| 322 | catch { case SystemError(ENOENT, _) => false }; |
| 323 | def fileExists(file: File): Boolean = fileExists(file.getPath); |
| 324 | |
| 325 | /*----- That's all, folks -------------------------------------------------*/ |
| 326 | |
| 327 | } |