| 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.{HashMap, HashSet}; |
| 32 | |
| 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; |
| 41 | |
| 42 | import Implicits.truish; |
| 43 | |
| 44 | /*----- Some magic for C strings ------------------------------------------*/ |
| 45 | |
| 46 | type CString = Array[Byte]; |
| 47 | |
| 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. |
| 52 | */ |
| 53 | |
| 54 | class InvalidCStringException(msg: String) extends Exception(msg); |
| 55 | |
| 56 | object StringImplicits { |
| 57 | implicit class ConvertJStringToCString(s: String) { |
| 58 | /* Magic to convert a string into a C string (null-terminated bytes). */ |
| 59 | |
| 60 | def toCString: CString = { |
| 61 | /* Convert the receiver to a C string. */ |
| 62 | |
| 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); |
| 67 | |
| 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. |
| 71 | */ |
| 72 | val r = if (in.hasRemaining) enc.encode(in, out, true) |
| 73 | else enc.flush(out); |
| 74 | |
| 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. */ |
| 79 | |
| 80 | sz *= 2; |
| 81 | val newout = ByteBuffer.allocate(sz); |
| 82 | out.flip(); newout.put(out); |
| 83 | out = newout; |
| 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. |
| 88 | */ |
| 89 | |
| 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); |
| 95 | v(n) = 0; |
| 96 | exit(v); |
| 97 | } |
| 98 | } |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | implicit class ConvertCStringToJString(v: CString) { |
| 103 | /* Magic to convert a C string into a `proper' string. */ |
| 104 | |
| 105 | def toJString: String = { |
| 106 | /* Convert the receiver to a C string. |
| 107 | * |
| 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 |
| 112 | * UTF-8 either. |
| 113 | */ |
| 114 | |
| 115 | val inlen = v.indexOf(0) match { |
| 116 | case -1 => v.length |
| 117 | case n => n |
| 118 | } |
| 119 | val dec = Charset.defaultCharset.newDecoder; |
| 120 | val in = ByteBuffer.wrap(v, 0, inlen); |
| 121 | dec.decode(in).toString |
| 122 | } |
| 123 | } |
| 124 | } |
| 125 | import StringImplicits._; |
| 126 | |
| 127 | /*----- Main code ---------------------------------------------------------*/ |
| 128 | |
| 129 | /* Import the native code library. */ |
| 130 | System.loadLibrary("tripe"); |
| 131 | |
| 132 | /* Native types. |
| 133 | * |
| 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. |
| 137 | */ |
| 138 | type Wrapper = Array[Byte]; |
| 139 | class NativeObjectTypeException(msg: String) extends RuntimeException(msg); |
| 140 | |
| 141 | /*----- Error codes -------------------------------------------------------*/ |
| 142 | |
| 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; |
| 147 | |
| 148 | object Errno extends Enumeration { |
| 149 | /* System errors. |
| 150 | * |
| 151 | * There are two slight difficulties here. |
| 152 | * |
| 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. |
| 156 | * |
| 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. |
| 161 | * |
| 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. |
| 168 | */ |
| 169 | |
| 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; |
| 173 | b.result |
| 174 | } |
| 175 | |
| 176 | private val seen = HashSet[Int](); // which error codes have been taken |
| 177 | |
| 178 | private var wrong = -256; // next synthetic code |
| 179 | private def nextwrong: Int = { val w = wrong; wrong -= 1; w } |
| 180 | |
| 181 | class Val private[Errno](tag: String, val code: Int, id: Int) |
| 182 | extends super.Val(id, tag) { |
| 183 | /* Our augmented error type. */ |
| 184 | |
| 185 | def message: String = strerror(code).toJString; |
| 186 | } |
| 187 | private class UnknownError(code: Int) |
| 188 | extends Val("<unknown>", code, code); |
| 189 | |
| 190 | private def err(tag: String, code: Int): Val = { |
| 191 | /* Construct an error symbol given its tag string and a code number. */ |
| 192 | |
| 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) } |
| 196 | } |
| 197 | private def err(tag: String): Val = |
| 198 | err(tag, tagmap.getOrElse(tag, nextwrong)); |
| 199 | |
| 200 | def byid(id: Int): Value = { |
| 201 | if (seen contains id) apply(id) |
| 202 | else new UnknownError(id) |
| 203 | } |
| 204 | |
| 205 | val OK = err("OK", 0); // `errno' zero is a real thing |
| 206 | |
| 207 | /* |
| 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. |
| 211 | |
| 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 |
| 216 | ERANGE |
| 217 | |
| 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))) |
| 235 | (save-excursion |
| 236 | (goto-char (point-min)) |
| 237 | (search-forward (concat "***" "BEGIN errtab" "***")) |
| 238 | (beginning-of-line 2) |
| 239 | (delete-region (point) |
| 240 | (progn |
| 241 | (search-forward "***END***") |
| 242 | (beginning-of-line) |
| 243 | (point))) |
| 244 | (dolist (err errors) |
| 245 | (insert (format " val %s = err(\"%s\");\n" err err))))) |
| 246 | */ |
| 247 | /***BEGIN errtab***/ |
| 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"); |
| 381 | /***end***/ |
| 382 | } |
| 383 | import Errno.{Val => Errno, EEXIST, EISDIR, ENOENT, ENOTDIR}; |
| 384 | |
| 385 | object SystemError { |
| 386 | /* Pattern matching for `SystemError', below. */ |
| 387 | |
| 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)) |
| 392 | case _ => None |
| 393 | } |
| 394 | } |
| 395 | class SystemError (val err: Errno, val what: String) extends Exception { |
| 396 | /* An error from a syscall or similar, usually from native code. */ |
| 397 | |
| 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); } |
| 401 | |
| 402 | override def getMessage(): String = s"$what: ${err.message}"; |
| 403 | } |
| 404 | |
| 405 | /*----- Basic file operations ---------------------------------------------*/ |
| 406 | |
| 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); } |
| 420 | |
| 421 | @native def fdint(fd: FileDescriptor): Int; |
| 422 | @native def newfd(fd: Int): FileDescriptor; |
| 423 | @native def isatty(fd: FileDescriptor): Boolean; |
| 424 | |
| 425 | /*----- File status information -------------------------------------------*/ |
| 426 | |
| 427 | /* These are the traditional values, but the C code carefully arranges to |
| 428 | * return them regardless of what your kernel actually thinks. |
| 429 | */ |
| 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; |
| 438 | |
| 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); |
| 444 | |
| 445 | object FileInfo extends Enumeration { |
| 446 | /* A simple enumeration of things a file might be. |
| 447 | * |
| 448 | * `HDLNK' is a hard link, used in `tar' files. |
| 449 | */ |
| 450 | val FIFO, CHR, DIR, BLK, REG, LNK, SOCK, HDLNK, UNK = Value; |
| 451 | type Type = Value; |
| 452 | } |
| 453 | import FileInfo._; |
| 454 | |
| 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, |
| 459 | val size: Long, |
| 460 | val blksize: Int, val blocks: Long, |
| 461 | val atime: Date, val mtime: Date, |
| 462 | val ctime: Date) { |
| 463 | /* Information about a file. This is constructed directly from native |
| 464 | * code. |
| 465 | */ |
| 466 | |
| 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'. */ |
| 473 | |
| 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)); |
| 477 | } |
| 478 | |
| 479 | /* Return the file permissions only. */ |
| 480 | def perms: Int = mode&0xfff; |
| 481 | |
| 482 | /* Return the filetype, as a `FileInfo.Type'. */ |
| 483 | def ftype: Type = (mode&S_IFMT) match { |
| 484 | case S_IFIFO => FIFO |
| 485 | case S_IFCHR => CHR |
| 486 | case S_IFDIR => DIR |
| 487 | case S_IFBLK => BLK |
| 488 | case S_IFREG => REG |
| 489 | case S_IFLNK => LNK |
| 490 | case S_IFSOCK => SOCK |
| 491 | case _ => UNK |
| 492 | } |
| 493 | |
| 494 | private[this] def mustBeDevice() { |
| 495 | /* Insist that you only ask for `rdev' fields on actual device nodes. */ |
| 496 | ftype match { |
| 497 | case CHR | BLK => ok; |
| 498 | case _ => throw new IllegalArgumentException("Object is not a device"); |
| 499 | } |
| 500 | } |
| 501 | |
| 502 | /* Query the device-node numbers. */ |
| 503 | def rdevMajor: Int = { mustBeDevice(); _rdevMajor } |
| 504 | def rdevMinor: Int = { mustBeDevice(); _rdevMinor } |
| 505 | } |
| 506 | |
| 507 | /*----- Listing directories -----------------------------------------------*/ |
| 508 | |
| 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); |
| 513 | |
| 514 | protected abstract class BaseDirIterator[T](cpath: CString) |
| 515 | extends LookaheadIterator[T] with Closeable { |
| 516 | /* The underlying machinery for directory iterators. |
| 517 | * |
| 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. |
| 522 | */ |
| 523 | |
| 524 | /* Constructors from more convenient types. */ |
| 525 | def this(path: String) { this(path.toCString); } |
| 526 | def this(dir: File) { this(dir.getPath); } |
| 527 | |
| 528 | /* Cleaning up after ourselves. */ |
| 529 | override def close() { closedir(cpath, dir); } |
| 530 | override protected def finalize() { super.finalize(); close(); } |
| 531 | |
| 532 | /* Subclass responsibility. */ |
| 533 | protected def mangle(file: String): T; |
| 534 | |
| 535 | /* Main machinery. */ |
| 536 | private[this] val dir = opendir(cpath); |
| 537 | override protected def fetch(): Option[T] = readdir(cpath, dir) match { |
| 538 | case null => None |
| 539 | case f => f.toJString match { |
| 540 | case "." | ".." => fetch() |
| 541 | case jf => Some(mangle(jf)) |
| 542 | } |
| 543 | } |
| 544 | } |
| 545 | |
| 546 | class DirIterator(val path: String) extends BaseDirIterator[String](path) { |
| 547 | /* Iterator over the basenames of files in a directory. */ |
| 548 | |
| 549 | def this(dir: File) { this(dir.getPath); } |
| 550 | |
| 551 | override protected def mangle(file: String): String = file; |
| 552 | } |
| 553 | |
| 554 | class DirFilesIterator private[this](val dir: File, cpath: CString) |
| 555 | extends BaseDirIterator[File](cpath) { |
| 556 | /* Iterator over full `File' objects in a directory. */ |
| 557 | |
| 558 | def this(dir: File) { this(dir, dir.getPath.toCString); } |
| 559 | def this(path: String) { this(new File(path), path.toCString); } |
| 560 | |
| 561 | override protected def mangle(file: String): File = new File(dir, file); |
| 562 | } |
| 563 | |
| 564 | /*----- File locking ------------------------------------------------------*/ |
| 565 | |
| 566 | /* Primitive operations. The low `mode' bits are for the lock file if we |
| 567 | * have to create it. |
| 568 | */ |
| 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); |
| 573 | |
| 574 | class FileLock(path: String, flags: Int) extends Closeable { |
| 575 | /* A class which represents a held lock on a file. */ |
| 576 | |
| 577 | /* Constructors. The default is to take an exclusive lock or fail |
| 578 | * immediately. |
| 579 | */ |
| 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); } |
| 583 | |
| 584 | /* The low-level lock object, actually a file descriptor. */ |
| 585 | private[this] val lk = lock(path.toCString, flags); |
| 586 | |
| 587 | /* Making sure things get cleaned up. */ |
| 588 | override def close() { unlock(lk); } |
| 589 | override protected def finalize() { super.finalize(); close(); } |
| 590 | } |
| 591 | |
| 592 | /*----- File implicits ----------------------------------------------------*/ |
| 593 | |
| 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 |
| 599 | * might explode. |
| 600 | * |
| 601 | * And some other useful methods. |
| 602 | */ |
| 603 | |
| 604 | /* Constructing names of files in a directory. Honestly, I'm surprised |
| 605 | * there isn't a method for this already. |
| 606 | */ |
| 607 | def /(sub: String): File = new File(file, sub); |
| 608 | |
| 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); } |
| 618 | |
| 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(); } |
| 623 | } |
| 624 | def foreachFile(fn: File => Unit) { withFilesIterator(_.foreach(fn)) } |
| 625 | def files_! : Seq[File] = withFilesIterator { _.toSeq }; |
| 626 | |
| 627 | /* Low-level lFile information. */ |
| 628 | def stat_! : FileInfo = stat(file.getPath); |
| 629 | def lstat_! : FileInfo = lstat(file.getPath); |
| 630 | |
| 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) |
| 639 | }; |
| 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); |
| 648 | |
| 649 | /* Slightly more cooked file operations. */ |
| 650 | def remove_!() { |
| 651 | /* Delete a file, or directory, whatever it is. */ |
| 652 | try { unlink_!(); return; } |
| 653 | catch { |
| 654 | case SystemError(ENOENT, _) => return; |
| 655 | case SystemError(EISDIR, _) => |
| 656 | try { rmdir_!(); return; } |
| 657 | catch { case SystemError(ENOENT, _) => return; } |
| 658 | } |
| 659 | } |
| 660 | |
| 661 | def rmTree() { |
| 662 | /* Delete a thing recursively. */ |
| 663 | def walk(f: File) { |
| 664 | if (f.isdir_!) f.foreachFile(walk _); |
| 665 | f.remove_!(); |
| 666 | } |
| 667 | walk(file); |
| 668 | } |
| 669 | |
| 670 | def mkdirNew_!() { |
| 671 | /* Make a directory if there's nothing there already. */ |
| 672 | try { mkdir_!(); } |
| 673 | catch { case SystemError(EEXIST, _) => ok; } |
| 674 | } |
| 675 | |
| 676 | /* File locking. */ |
| 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(); } |
| 682 | } |
| 683 | def withLock[T](body: => T): T = withLock(LKF_EXCL | 0x1b6) { body }; |
| 684 | |
| 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 = { |
| 691 | val in = open(); |
| 692 | try { body(in) } |
| 693 | finally { in.close(); } |
| 694 | } |
| 695 | def withOutput[T](body: FileOutputStream => T): T = { |
| 696 | val out = openForOutput(); |
| 697 | try { body(out) } finally { out.close(); } |
| 698 | } |
| 699 | def withReader[T](body: BufferedReader => T): T = { |
| 700 | val r = reader(); |
| 701 | try { body(r) } |
| 702 | finally { r.close(); } |
| 703 | } |
| 704 | def withWriter[T](body: BufferedWriter => T): T = { |
| 705 | val w = writer(); |
| 706 | try { body(w) } |
| 707 | finally { w.close(); } |
| 708 | } |
| 709 | } |
| 710 | } |
| 711 | import FileImplicits._; |
| 712 | |
| 713 | /*----- Miscellaneous file hacks ------------------------------------------*/ |
| 714 | |
| 715 | def freshFile(d: File): File = { |
| 716 | /* Return the name of a freshly created file in directory D. */ |
| 717 | |
| 718 | val buf = new Array[Byte](6); |
| 719 | val b = new StringBuilder; |
| 720 | |
| 721 | loop[File] { exit => |
| 722 | /* Keep going until we find a fresh one. */ |
| 723 | |
| 724 | /* Provide a prefix. Mostly this is to prevent the file starting with |
| 725 | * an unfortunate character like `-'. |
| 726 | */ |
| 727 | b ++= "tmp."; |
| 728 | |
| 729 | /* Generate some random bytes. */ |
| 730 | rng.nextBytes(buf); |
| 731 | |
| 732 | /* Now turn the bytes into a filename. This is a cheesy implementation |
| 733 | * of Base64 encoding. |
| 734 | */ |
| 735 | var a = 0; |
| 736 | var n = 0; |
| 737 | |
| 738 | for (x <- buf) { |
| 739 | a = (a << 8) | x; n += 8; |
| 740 | while (n >= 6) { |
| 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) '+' |
| 746 | else '-').toChar; |
| 747 | } |
| 748 | } |
| 749 | |
| 750 | /* Make the filename, and try to create the file. If we succeed, we |
| 751 | * win. |
| 752 | */ |
| 753 | val f = d/b.result; b.clear(); |
| 754 | try { f.mkfile_!(); exit(f); } |
| 755 | catch { case SystemError(EEXIST, _) => ok; } |
| 756 | } |
| 757 | } |
| 758 | |
| 759 | /*----- Running a command -------------------------------------------------*/ |
| 760 | |
| 761 | private val devnull = new File("/dev/null"); |
| 762 | |
| 763 | private def captureStream(in: InputStream, out: StringBuilder) { |
| 764 | /* Capture the INSTREAM's contents in a string. */ |
| 765 | |
| 766 | for ((buf, n) <- blocks(new InputStreamReader(in))) |
| 767 | out.appendAll(buf, 0, n); |
| 768 | } |
| 769 | |
| 770 | class SubprocessFailed(val cmd: Seq[String], rc: Int, stderr: String) |
| 771 | extends Exception { |
| 772 | override def getMessage(): String = |
| 773 | s"process (${quoteTokens(cmd)}) failed (rc = $rc):\n" + stderr |
| 774 | } |
| 775 | |
| 776 | def runCommand(cmd: String*): (String, String) = { |
| 777 | /* Run a command, returning its stdout and stderr. */ |
| 778 | |
| 779 | withCleaner { clean => |
| 780 | |
| 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(); } |
| 787 | |
| 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. |
| 791 | */ |
| 792 | val bout, berr = new StringBuilder; |
| 793 | val rdout = thread("capture process stdout", daemon = false) { |
| 794 | captureStream(out, bout); |
| 795 | } |
| 796 | val rderr = thread("capture process stderr", daemon = false) { |
| 797 | captureStream(err, berr); |
| 798 | } |
| 799 | val wait = thread("await process exit", daemon = false) { |
| 800 | kid.waitFor(); |
| 801 | } |
| 802 | rdout.join(); rderr.join(); wait.join(); |
| 803 | |
| 804 | /* Check the exit status. */ |
| 805 | val rc = kid.exitValue; |
| 806 | if (rc) throw new SubprocessFailed(cmd, rc, berr.result); |
| 807 | |
| 808 | /* We're all done. */ |
| 809 | return (bout.result, berr.result); |
| 810 | } |
| 811 | } |
| 812 | |
| 813 | /*----- Interrupt triggers ------------------------------------------------*/ |
| 814 | |
| 815 | private val triggerLock = new Object; |
| 816 | private final val maxTriggers = 2; |
| 817 | private var nTriggers = 0; |
| 818 | private var triggers: List[Wrapper] = Nil; |
| 819 | |
| 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); |
| 824 | |
| 825 | private def getTrigger(): Wrapper = { |
| 826 | triggerLock synchronized { |
| 827 | if (!nTriggers) |
| 828 | make_trigger() |
| 829 | else { |
| 830 | val trig = triggers.head; |
| 831 | triggers = triggers.tail; |
| 832 | nTriggers -= 1; |
| 833 | trig |
| 834 | } |
| 835 | } |
| 836 | } |
| 837 | |
| 838 | private def putTrigger(trig: Wrapper) { |
| 839 | reset_trigger(trig); |
| 840 | triggerLock synchronized { |
| 841 | if (nTriggers >= maxTriggers) |
| 842 | destroy_trigger(trig); |
| 843 | else { |
| 844 | triggers ::= trig; |
| 845 | nTriggers += 1; |
| 846 | } |
| 847 | } |
| 848 | } |
| 849 | |
| 850 | private def withTrigger[T](body: Wrapper => T): T = { |
| 851 | val trig = getTrigger(); |
| 852 | try { body(trig) } |
| 853 | finally { putTrigger(trig); } |
| 854 | } |
| 855 | |
| 856 | def interruptWithTrigger[T](body: Wrapper => T): T = { |
| 857 | /* interruptWithTrigger { TRIG => BODY } |
| 858 | * |
| 859 | * Execute BODY and return its result. If the thread receives an |
| 860 | * interrupt, the trigger TRIG will be pulled. See `interruptably' for the |
| 861 | * full semantics. |
| 862 | */ |
| 863 | |
| 864 | withTrigger { trig => |
| 865 | interruptably { body(trig) } onInterrupt { trigger(trig); } |
| 866 | }; |
| 867 | } |
| 868 | |
| 869 | /*----- Glue for the VPN server -------------------------------------------*/ |
| 870 | |
| 871 | /* The lock class. This is only a class because they're much easier to find |
| 872 | * than loose objects through JNI. |
| 873 | */ |
| 874 | private class ServerLock; |
| 875 | |
| 876 | /* Exceptions. */ |
| 877 | class NameResolutionException(msg: String) extends Exception(msg); |
| 878 | class InitializationException(msg: String) extends Exception(msg); |
| 879 | |
| 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); |
| 889 | @native def run(); |
| 890 | @native protected def send(buf: CString, start: Int, len: Int, |
| 891 | trig: Wrapper); |
| 892 | @native protected def recv(buf: CString, start: Int, len: Int, |
| 893 | trig: Wrapper): Int; |
| 894 | |
| 895 | base_init(); |
| 896 | setup_resolver(); |
| 897 | |
| 898 | /* Tunnel descriptor plumbing. */ |
| 899 | val pending = HashMap[String, Int](); |
| 900 | |
| 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); } |
| 910 | } |
| 911 | |
| 912 | /* Server I/O. */ |
| 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; |
| 918 | } |
| 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() { } |
| 924 | } |
| 925 | |
| 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() { } |
| 932 | } |
| 933 | |
| 934 | /*----- Crypto-library hacks ----------------------------------------------*/ |
| 935 | |
| 936 | @native def hashsz(hash: String): Int; |
| 937 | /* Return the output hash size for the named HASH function, or -1. */ |
| 938 | |
| 939 | /*----- That's all, folks -------------------------------------------------*/ |
| 940 | |
| 941 | } |