shake it all up
[tripe-android] / sys.scala
CommitLineData
8eabb4ff
MW
1/* -*-scala-*-
2 *
25c35469 3 * System-level hacking
8eabb4ff
MW
4 *
5 * (c) 2018 Straylight/Edgeware
6 */
7
8/*----- Licensing notice --------------------------------------------------*
9 *
10 * This file is part of the Trivial IP Encryption (TrIPE) Android app.
11 *
12 * TrIPE is free software: you can redistribute it and/or modify it under
13 * the terms of the GNU General Public License as published by the Free
14 * Software Foundation; either version 3 of the License, or (at your
15 * option) any later version.
16 *
17 * TrIPE is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
20 * for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with TrIPE. If not, see <https://www.gnu.org/licenses/>.
24 */
25
26package uk.org.distorted.tripe; package object sys {
27
28/*----- Imports -----------------------------------------------------------*/
29
30import scala.collection.mutable.HashSet;
31
25c35469
MW
32import java.io.{Closeable, File};
33import java.nio.{ByteBuffer, CharBuffer};
34import java.nio.charset.Charset;
35import java.util.Date;
8eabb4ff 36
25c35469
MW
37/*----- Some magic for C strings ------------------------------------------*/
38
39type CString = Array[Byte];
40
41/* We do this by hand, rather than relying on the JNI's built-in conversions,
42 * because we use the default encoding taken from the locale settings, rather
43 * than the ridiculous `modified UTF-8' which is (a) insensitive to the
44 * user's chosen locale and (b) not actually UTF-8 either.
45 */
46
47class InvalidCStringException(msg: String) extends Exception(msg);
48
49object StringImplicits {
50 implicit class ConvertJStringToCString(s: String) {
51 /* Magic to convert a string into a C string (null-terminated bytes). */
52
53 def toCString: CString = {
54 /* Convert the receiver to a C string. */
55
56 val enc = Charset.defaultCharset.newEncoder;
57 val in = CharBuffer.wrap(s);
58 var sz: Int = (s.length*enc.averageBytesPerChar + 1).toInt;
59 var out = ByteBuffer.allocate(sz);
60
61 while (true) {
62 /* If there's still stuff to encode, then encode it. Otherwise,
63 * there must be some dregs left in the encoder, so flush them out.
64 */
65 val r = if (in.hasRemaining) enc.encode(in, out, true)
66 else enc.flush(out);
67
68 /* Sift through the wreckage to figure out what to do. */
69 if (r.isError) r.throwException();
70 else if (r.isOverflow) {
71 /* No space in the buffer. Make it bigger. */
72
73 sz *= 2;
74 val newout = ByteBuffer.allocate(sz);
75 out.flip(); newout.put(out);
76 out = newout;
77 } else if (r.isUnderflow) {
78 /* All done. Check that there are no unexpected zero bytes -- so
79 * this will indeed be a valid C string -- and convert into a byte
80 * array that the C code will be able to pick apart.
81 */
82
83 out.flip(); val n = out.limit; val u = out.array;
84 if ({val z = u.indexOf(0); 0 <= z && z < n})
85 throw new InvalidCStringException("null byte in encoding");
86 val v = new Array[Byte](n + 1);
87 out.array.copyToArray(v, 0, n);
88 v(n) = 0;
89 return v;
90 }
91 }
92
93 /* Placate the type checker. */
94 unreachable("unreachable");
95 }
96 }
97
98 implicit class ConvertCStringToJString(v: CString) {
99 /* Magic to convert a C string into a `proper' string. */
100
101 def toJString: String = {
102 /* Convert the receiver to a C string.
103 *
104 * We do this by hand, rather than relying on the JNI's built-in
105 * conversions, because we use the default encoding taken from the
106 * locale settings, rather than the ridiculous `modified UTF-8' which
107 * is (a) insensitive to the user's chosen locale and (b) not actually
108 * UTF-8 either.
109 */
110
111 val inlen = v.indexOf(0) match {
112 case -1 => v.length
113 case n => n
114 }
115 val dec = Charset.defaultCharset.newDecoder;
116 val in = ByteBuffer.wrap(v, 0, inlen);
117 dec.decode(in).toString
118 }
119 }
120}
121import StringImplicits._;
122
123/*----- Main code ---------------------------------------------------------*/
124
125/* Import the native code library. */
126System.loadLibrary("toy");
127
128/* Exception indicating that a wrapped native object has been clobbered. */
129class NativeObjectTypeException(msg: String) extends RuntimeException(msg);
130type Wrapper = Array[Byte];
8eabb4ff
MW
131
132/*----- Error codes -------------------------------------------------------*/
133
25c35469
MW
134protected case class ErrorEntry(val tag: String, val err: Int);
135@native protected def errtab: Array[ErrorEntry];
136@native protected def strerror(err: Int): CString;
137
8eabb4ff 138object Errno extends Enumeration {
25c35469 139 private val tagmap = {
8eabb4ff 140 val b = Map.newBuilder[String, Int];
25c35469 141 for (ErrorEntry(tag, err) <- errtab) b += tag -> err;
8eabb4ff
MW
142 b.result
143 }
25c35469
MW
144 private var wrong = -255;
145 private val seen = HashSet[Int]();
8eabb4ff
MW
146
147 class ErrnoVal private[Errno](tag: String, val code: Int, id: Int)
148 extends Val(id, tag) {
25c35469 149 def message: String = strerror(code).toJString;
8eabb4ff
MW
150 }
151
25c35469 152 private def err(tag: String, code: Int): ErrnoVal = {
8eabb4ff
MW
153 if (seen contains code) { wrong -= 1; new ErrnoVal(tag, code, wrong) }
154 else { seen += code; new ErrnoVal(tag, code, code) }
155 }
25c35469 156 private def err(tag: String): ErrnoVal = err(tag, tagmap(tag));
8eabb4ff
MW
157
158 val OK = err("OK", 0);
159
160 /*
161 ;;; The errno name table is very boring to type. To make life less
162 ;;; awful, put the errno names in this list and evaluate the code to
163 ;;; get Emacs to regenerate it.
164
165 (let ((errors '(EPERM ENOENT ESRCH EINTR EIO ENXIO E2BIG ENOEXEC EBADF
166 ECHILD EAGAIN ENOMEM EACCES EFAULT ENOTBLK EBUSY EEXIST
167 EXDEV ENODEV ENOTDIR EISDIR EINVAL ENFILE EMFILE ENOTTY
168 ETXTBSY EFBIG ENOSPC ESPIPE EROFS EMLINK EPIPE EDOM
169 ERANGE
170
171 EDEADLK ENAMETOOLONG ENOLCK ENOSYS ENOTEMPTY ELOOP
172 EWOULDBLOCK ENOMSG EIDRM ECHRNG EL2NSYNC EL3HLT EL3RST
173 ELNRNG EUNATCH ENOCSI EL2HLT EBADE EBADR EXFULL ENOANO
174 EBADRQC EBADSLT EDEADLOCK EBFONT ENOSTR ENODATA ETIME
175 ENOSR ENONET ENOPKG EREMOTE ENOLINK EADV ESRMNT ECOMM
176 EPROTO EMULTIHOP EDOTDOT EBADMSG EOVERFLOW ENOTUNIQ
177 EBADFD EREMCHG ELIBACC ELIBBAD ELIBSCN ELIBMAX ELIBEXEC
178 EILSEQ ERESTART ESTRPIPE EUSERS ENOTSOCK EDESTADDRREQ
179 EMSGSIZE EPROTOTYPE ENOPROTOOPT EPROTONOSUPPORT
180 ESOCKTNOSUPPORT EOPNOTSUPP EPFNOSUPPORT EAFNOSUPPORT
181 EADDRINUSE EADDRNOTAVAIL ENETDOWN ENETUNREACH ENETRESET
182 ECONNABORTED ECONNRESET ENOBUFS EISCONN ENOTCONN
183 ESHUTDOWN ETOOMANYREFS ETIMEDOUT ECONNREFUSED EHOSTDOWN
184 EHOSTUNREACH EALREADY EINPROGRESS ESTALE EUCLEAN ENOTNAM
185 ENAVAIL EISNAM EREMOTEIO EDQUOT ENOMEDIUM EMEDIUMTYPE
186 ECANCELED ENOKEY EKEYEXPIRED EKEYREVOKED EKEYREJECTED
187 EOWNERDEAD ENOTRECOVERABLE ERFKILL EHWPOISON)))
188 (save-excursion
189 (goto-char (point-min))
190 (search-forward (concat "***" "BEGIN errtab" "***"))
191 (beginning-of-line 2)
192 (delete-region (point)
193 (progn
194 (search-forward "***END***")
195 (beginning-of-line)
196 (point)))
197 (dolist (err errors)
198 (insert (format " val %s = err(\"%s\");\n" err err)))))
199 */
200 /***BEGIN errtab***/
201 val EPERM = err("EPERM");
202 val ENOENT = err("ENOENT");
203 val ESRCH = err("ESRCH");
204 val EINTR = err("EINTR");
205 val EIO = err("EIO");
206 val ENXIO = err("ENXIO");
207 val E2BIG = err("E2BIG");
208 val ENOEXEC = err("ENOEXEC");
209 val EBADF = err("EBADF");
210 val ECHILD = err("ECHILD");
211 val EAGAIN = err("EAGAIN");
212 val ENOMEM = err("ENOMEM");
213 val EACCES = err("EACCES");
214 val EFAULT = err("EFAULT");
215 val ENOTBLK = err("ENOTBLK");
216 val EBUSY = err("EBUSY");
217 val EEXIST = err("EEXIST");
218 val EXDEV = err("EXDEV");
219 val ENODEV = err("ENODEV");
220 val ENOTDIR = err("ENOTDIR");
221 val EISDIR = err("EISDIR");
222 val EINVAL = err("EINVAL");
223 val ENFILE = err("ENFILE");
224 val EMFILE = err("EMFILE");
225 val ENOTTY = err("ENOTTY");
226 val ETXTBSY = err("ETXTBSY");
227 val EFBIG = err("EFBIG");
228 val ENOSPC = err("ENOSPC");
229 val ESPIPE = err("ESPIPE");
230 val EROFS = err("EROFS");
231 val EMLINK = err("EMLINK");
232 val EPIPE = err("EPIPE");
233 val EDOM = err("EDOM");
234 val ERANGE = err("ERANGE");
235 val EDEADLK = err("EDEADLK");
236 val ENAMETOOLONG = err("ENAMETOOLONG");
237 val ENOLCK = err("ENOLCK");
238 val ENOSYS = err("ENOSYS");
239 val ENOTEMPTY = err("ENOTEMPTY");
240 val ELOOP = err("ELOOP");
241 val EWOULDBLOCK = err("EWOULDBLOCK");
242 val ENOMSG = err("ENOMSG");
243 val EIDRM = err("EIDRM");
244 val ECHRNG = err("ECHRNG");
245 val EL2NSYNC = err("EL2NSYNC");
246 val EL3HLT = err("EL3HLT");
247 val EL3RST = err("EL3RST");
248 val ELNRNG = err("ELNRNG");
249 val EUNATCH = err("EUNATCH");
250 val ENOCSI = err("ENOCSI");
251 val EL2HLT = err("EL2HLT");
252 val EBADE = err("EBADE");
253 val EBADR = err("EBADR");
254 val EXFULL = err("EXFULL");
255 val ENOANO = err("ENOANO");
256 val EBADRQC = err("EBADRQC");
257 val EBADSLT = err("EBADSLT");
258 val EDEADLOCK = err("EDEADLOCK");
259 val EBFONT = err("EBFONT");
260 val ENOSTR = err("ENOSTR");
261 val ENODATA = err("ENODATA");
262 val ETIME = err("ETIME");
263 val ENOSR = err("ENOSR");
264 val ENONET = err("ENONET");
265 val ENOPKG = err("ENOPKG");
266 val EREMOTE = err("EREMOTE");
267 val ENOLINK = err("ENOLINK");
268 val EADV = err("EADV");
269 val ESRMNT = err("ESRMNT");
270 val ECOMM = err("ECOMM");
271 val EPROTO = err("EPROTO");
272 val EMULTIHOP = err("EMULTIHOP");
273 val EDOTDOT = err("EDOTDOT");
274 val EBADMSG = err("EBADMSG");
275 val EOVERFLOW = err("EOVERFLOW");
276 val ENOTUNIQ = err("ENOTUNIQ");
277 val EBADFD = err("EBADFD");
278 val EREMCHG = err("EREMCHG");
279 val ELIBACC = err("ELIBACC");
280 val ELIBBAD = err("ELIBBAD");
281 val ELIBSCN = err("ELIBSCN");
282 val ELIBMAX = err("ELIBMAX");
283 val ELIBEXEC = err("ELIBEXEC");
284 val EILSEQ = err("EILSEQ");
285 val ERESTART = err("ERESTART");
286 val ESTRPIPE = err("ESTRPIPE");
287 val EUSERS = err("EUSERS");
288 val ENOTSOCK = err("ENOTSOCK");
289 val EDESTADDRREQ = err("EDESTADDRREQ");
290 val EMSGSIZE = err("EMSGSIZE");
291 val EPROTOTYPE = err("EPROTOTYPE");
292 val ENOPROTOOPT = err("ENOPROTOOPT");
293 val EPROTONOSUPPORT = err("EPROTONOSUPPORT");
294 val ESOCKTNOSUPPORT = err("ESOCKTNOSUPPORT");
295 val EOPNOTSUPP = err("EOPNOTSUPP");
296 val EPFNOSUPPORT = err("EPFNOSUPPORT");
297 val EAFNOSUPPORT = err("EAFNOSUPPORT");
298 val EADDRINUSE = err("EADDRINUSE");
299 val EADDRNOTAVAIL = err("EADDRNOTAVAIL");
300 val ENETDOWN = err("ENETDOWN");
301 val ENETUNREACH = err("ENETUNREACH");
302 val ENETRESET = err("ENETRESET");
303 val ECONNABORTED = err("ECONNABORTED");
304 val ECONNRESET = err("ECONNRESET");
305 val ENOBUFS = err("ENOBUFS");
306 val EISCONN = err("EISCONN");
307 val ENOTCONN = err("ENOTCONN");
308 val ESHUTDOWN = err("ESHUTDOWN");
309 val ETOOMANYREFS = err("ETOOMANYREFS");
310 val ETIMEDOUT = err("ETIMEDOUT");
311 val ECONNREFUSED = err("ECONNREFUSED");
312 val EHOSTDOWN = err("EHOSTDOWN");
313 val EHOSTUNREACH = err("EHOSTUNREACH");
314 val EALREADY = err("EALREADY");
315 val EINPROGRESS = err("EINPROGRESS");
316 val ESTALE = err("ESTALE");
317 val EUCLEAN = err("EUCLEAN");
318 val ENOTNAM = err("ENOTNAM");
319 val ENAVAIL = err("ENAVAIL");
320 val EISNAM = err("EISNAM");
321 val EREMOTEIO = err("EREMOTEIO");
322 val EDQUOT = err("EDQUOT");
323 val ENOMEDIUM = err("ENOMEDIUM");
324 val EMEDIUMTYPE = err("EMEDIUMTYPE");
325 val ECANCELED = err("ECANCELED");
326 val ENOKEY = err("ENOKEY");
327 val EKEYEXPIRED = err("EKEYEXPIRED");
328 val EKEYREVOKED = err("EKEYREVOKED");
329 val EKEYREJECTED = err("EKEYREJECTED");
330 val EOWNERDEAD = err("EOWNERDEAD");
331 val ENOTRECOVERABLE = err("ENOTRECOVERABLE");
332 val ERFKILL = err("ERFKILL");
333 val EHWPOISON = err("EHWPOISON");
334 /***end***/
335}
336import Errno.{Value => _, _};
337
338object SystemError {
339 def apply(err: Errno.Value, what: String): SystemError =
340 new SystemError(err, what);
341 def unapply(e: Exception): Option[(Errno.Value, String)] = e match {
342 case e: SystemError => Some((e.err, e.what))
343 case _ => None
344 }
345}
346
347class SystemError private[this](val err: Errno.ErrnoVal, val what: String)
348 extends Exception {
349 def this(err: Errno.Value, what: String)
350 { this(err.asInstanceOf[Errno.ErrnoVal], what); }
25c35469 351 private def this(err: Int, what: CString)
8eabb4ff
MW
352 { this(Errno(err), what.toJString); }
353 override def getMessage(): String = s"$what: ${err.message}";
354}
355
25c35469
MW
356/*----- Basic file operations ---------------------------------------------*/
357
358@native protected def unlink(path: CString);
359def unlink(path: String) { unlink(path.toCString); }
360@native protected def rmdir(path: CString);
361def rmdir(path: String) { rmdir(path.toCString); }
362@native protected def mkdir(path: CString, mode: Int);
363def mkdir(path: String, mode: Int) { mkdir(path.toCString, mode); }
364@native protected def mkfile(path: CString, mode: Int);
365def mkfile(path: String, mode: Int) { mkfile(path.toCString, mode); }
366@native protected def rename(from: CString, to: CString);
367def rename(from: String, to: String)
368 { rename(from.toCString, to.toCString); }
369
370/*----- File status information -------------------------------------------*/
371
372/* These are the traditional values, but the C code carefully arranges to
373 * return them regardless of what your kernel actually thinks.
374 */
375val S_IFMT = 0xf000;
376val S_IFIFO = 0x1000;
377val S_IFCHR = 0x2000;
378val S_IFDIR = 0x4000;
379val S_IFBLK = 0x6000;
380val S_IFREG = 0x8000;
381val S_IFLNK = 0xa000;
382val S_IFSOCK = 0xc000;
383
384@native protected def stat(path: CString): sys.FileInfo;
385def stat(path: String): sys.FileInfo = stat(path.toCString);
386@native protected def lstat(path: CString): sys.FileInfo;
387def lstat(path: String): sys.FileInfo = lstat(path.toCString);
388
389object FileInfo extends Enumeration {
390 val FIFO, CHR, DIR, BLK, REG, LNK, SOCK, UNK = Value;
391 type Type = Value;
392}
393import FileInfo._;
394
395
396class FileInfo private[this](val devMajor: Int, val devMinor: Int,
397 val ino: Long, val mode: Int, val nlink: Int,
398 val uid: Int, val gid: Int,
399 _rdevMinor: Int, _rdevMajor: Int,
400 val size: Long,
401 val blksize: Int, val blocks: Long,
402 val atime: Date, val mtime: Date,
403 val ctime: Date) {
404 private def this(devMajor: Int, devMinor: Int, ino: Long,
405 mode: Int, nlink: Int, uid: Int, gid: Int,
406 rdevMinor: Int, rdevMajor: Int,
407 size: Long, blksize: Int, blocks: Long,
408 atime: Long, mtime: Long, ctime: Long) {
409 this(devMajor, devMinor, ino, mode, nlink, uid, gid,
410 rdevMajor, rdevMinor, size, blksize, blocks,
411 new Date(atime), new Date(mtime), new Date(ctime));
412 }
413
414 def perms: Int = mode&0xfff;
415
416 def ftype: Type = (mode&S_IFMT) match {
417 case S_IFIFO => FIFO
418 case S_IFCHR => CHR
419 case S_IFDIR => DIR
420 case S_IFBLK => BLK
421 case S_IFREG => REG
422 case S_IFLNK => LNK
423 case S_IFSOCK => SOCK
424 case _ => UNK
425 }
426
427 private[this] def mustBeDevice() {
428 ftype match {
429 case CHR | BLK => ();
430 case _ => throw new IllegalArgumentException("Object is not a device");
431 }
432 }
433 def rdevMajor: Int = { mustBeDevice(); _rdevMajor }
434 def rdevMinor: Int = { mustBeDevice(); _rdevMinor }
435}
436
437/*----- Listing directories -----------------------------------------------*/
438
439@native protected def opendir(path: CString): Wrapper;
440@native protected def readdir(path: CString, dir: Wrapper): CString;
441@native protected def closedir(path: CString, dir: Wrapper);
442
443protected abstract class BaseDirIterator[T](cpath: CString)
444 extends LookaheadIterator[T] with Closeable {
445 def this(path: String) { this(path.toCString); }
446 def this(dir: File) { this(dir.getPath); }
447 override def close() { closedir(cpath, dir); }
448 override protected def finalize() { super.finalize(); close(); }
449 private[this] val dir = opendir(cpath);
450 protected def mangle(file: String): T;
451 override protected def fetch(): Option[T] = readdir(cpath, dir) match {
452 case null => None
453 case f => f.toJString match {
454 case "." | ".." => fetch()
455 case jf => Some(mangle(jf))
456 }
457 }
458}
459
460class DirIterator(val path: String) extends BaseDirIterator[String](path) {
461 def this(dir: File) { this(dir.getPath); }
462 override protected def mangle(file: String): String = file;
463}
464
465class DirFilesIterator private[this](val dir: File, cpath: CString)
466 extends BaseDirIterator[File](cpath) {
467 def this(dir: File) { this(dir, dir.getPath.toCString); }
468 def this(path: String) { this(new File(path), path.toCString); }
469 override protected def mangle(file: String): File = new File(dir, file);
470}
471
472/*----- File locking ------------------------------------------------------*/
473
474val LKF_EXCL = 1;
475val LKF_WAIT = 2;
476@native protected def lock(path: CString, flags: Int): Wrapper;
477@native protected def unlock(lock: Wrapper);
478
479class FileLock(path: String, flags: Int) extends Closeable {
480 def this(file: File, flags: Int) { this(file.getPath, flags); }
481 def this(path: String) { this(path, LKF_EXCL); }
482 def this(file: File) { this(file.getPath, LKF_EXCL); }
483 private[this] val lk = lock(path.toCString, flags);
484 override def close() { unlock(lk); }
485 override protected def finalize() { super.finalize(); close(); }
486}
487
488/*----- File implicits ----------------------------------------------------*/
489
490object FileImplicits {
491 implicit class FileOps(file: File) {
492
493 def unlink_!() { unlink(file.getPath); }
494 def rmdir_!() { rmdir(file.getPath); }
495 def mkdir_!(mode: Int) { mkdir(file.getPath, mode); }
496 def mkdir_!() { mkdir_!(0x1ff); }
497 def mkfile_!(mode: Int) { mkfile(file.getPath, mode); }
498 def mkfile_!() { mkfile_!(0x1b6); }
499
500 def withFilesIterator[T](body: DirFilesIterator => T): T = {
501 val iter = new DirFilesIterator(file.getPath);
502 try { body(iter) } finally { iter.close(); }
503 }
504 def files_! : Seq[File] = withFilesIterator { _.toSeq };
505
506 def stat_! : FileInfo = stat(file.getPath);
507 def lstat_! : FileInfo = lstat(file.getPath);
508
509 private[this] def statish[T](statfn: String => FileInfo,
510 ifexists: FileInfo => T,
511 ifmissing: => T): T =
512 (try { statfn(file.getPath) }
513 catch { case SystemError(ENOENT, _) => null }) match {
514 case null => ifmissing
515 case st => ifexists(st)
516 };
517 def exists_! : Boolean = statish(stat _, _ => true, false);
518 def isfifo_! : Boolean = statish(stat _, _.ftype == FIFO, false);
519 def isblk_! : Boolean = statish(stat _, _.ftype == BLK, false);
520 def isdir_! : Boolean = statish(stat _, _.ftype == DIR, false);
521 def ischr_! : Boolean = statish(stat _, _.ftype == CHR, false);
522 def isreg_! : Boolean = statish(stat _, _.ftype == REG, false);
523 def islnk_! : Boolean = statish(lstat _, _.ftype == LNK, false);
524 def issock_! : Boolean = statish(stat _, _.ftype == SOCK, false);
525
526 def remove_!() {
527 while (true) {
528 try { unlink_!(); return }
529 catch {
530 case SystemError(ENOENT, _) => return;
531 case SystemError(EISDIR, _) => ();
532 }
533 try { rmdir_!(); return }
534 catch {
535 case SystemError(ENOENT, _) => return;
536 case SystemError(ENOTDIR, _) => ();
537 }
538 }
539 }
540
541 def rmTree() {
542 def walk(f: File) {
543 if (f.isdir_!) f.withFilesIterator { _ foreach(walk _) };
544 f.remove_!();
545 }
546 walk(file);
547 }
548
549 def withLock[T](flags: Int)(body: => T): T = {
550 val lk = new FileLock(file.getPath, flags);
551 try { body } finally { lk.close(); }
552 }
553 def withLock[T](body: => T): T = withLock(LKF_EXCL) { body };
554 }
555}
556import FileImplicits._;
557
558/*----- Miscellaneous file hacks ------------------------------------------*/
8eabb4ff
MW
559
560def freshFile(d: File): File = {
561 /* Return the name of a freshly created file in directory D. */
562
563 val buf = new Array[Byte](6);
564 val b = new StringBuilder;
565
566 while (true) {
567 /* Keep going until we find a fresh one. */
568
569 /* Provide a prefix. Mostly this is to prevent the file starting with
570 * an unfortunate character like `-'.
571 */
572 b ++= "tmp.";
573
574 /* Generate some random bytes. */
575 rng.nextBytes(buf);
576
577 /* Now turn the bytes into a filename. This is a cheesy implementation
578 * of Base64 encoding.
579 */
580 var a = 0;
581 var n = 0;
582
583 for (x <- buf) {
584 a = (a << 8) | x; n += 8;
585 while (n >= 6) {
586 val y = (a >> n - 6)&0x3f; n -= 6;
587 b += (if (y < 26) 'A' + y
588 else if (y < 52) 'a' + (y - 26)
589 else if (y < 62) '0' + (y - 52)
590 else if (y == 62) '+'
591 else '-').toChar;
592 }
593 }
594
595 /* Make the filename, and try to create the file. If we succeed, we
596 * win.
597 */
598 val f = new File(d, b.result); b.clear();
25c35469 599 try { f.mkfile_!(); return f; }
8eabb4ff
MW
600 catch { case SystemError(EEXIST, _) => (); }
601 }
602
603 /* We shouldn't get here, but the type checker needs placating. */
604 unreachable("unreachable");
605}
606
25c35469
MW
607/*----- Connecting to a server --------------------------------------------*/
608
609val CF_CLOSERD = 1;
610val CF_CLOSEWR = 2;
611val CF_CLOSEMASK = CF_CLOSERD | CF_CLOSEWR;
612@native protected def connect(path: CString): Wrapper;
613@native protected def send(conn: Wrapper, buf: CString,
614 start: Int, len: Int);
615@native protected def recv(conn: Wrapper, buf: CString,
616 start: Int, len: Int): Int;
617@native def closeconn(conn: Wrapper, how: Int);
618
619class Connection(path: String) extends Closeable {
620 def this(file: File) { this(file.getPath); }
621 private[this] val conn = connect(path.toCString);
622 override def close() { closeconn(conn, CF_CLOSEMASK); }
623 override protected def finalize() { super.finalize(); close(); }
624
625 class InputStream private[Connection] extends java.io.InputStream {
626 override def read(): Int = {
627 val buf = new Array[Byte](1);
628 val n = read(buf, 0, 1);
629 if (n < 0) -1 else buf(0)&0xff;
8eabb4ff 630 }
25c35469
MW
631 override def read(buf: Array[Byte]): Int =
632 read(buf, 0, buf.length);
633 override def read(buf: Array[Byte], start: Int, len: Int) =
634 recv(conn, buf, start, len);
635 override def close() { closeconn(conn, CF_CLOSERD); }
8eabb4ff 636 }
25c35469
MW
637 lazy val input = new InputStream;
638
639 class OutputStream private[Connection] extends java.io.OutputStream {
640 override def write(b: Int) { write(Array[Byte](b.toByte), 0, 1); }
641 override def write(buf: Array[Byte]) { write(buf, 0, buf.length); }
642 override def write(buf: Array[Byte], start: Int, len: Int)
643 { send(conn, buf, start, len); }
644 override def close() { closeconn(conn, CF_CLOSEWR); }
645 }
646 lazy val output = new OutputStream;
8eabb4ff 647}
8eabb4ff 648
25c35469
MW
649/*----- Crypto-library hacks ----------------------------------------------*/
650
651@native def hashsz(hash: String): Int;
652 /* Return the output hash size for the named HASH function, or -1. */
8eabb4ff
MW
653
654/*----- That's all, folks -------------------------------------------------*/
655
656}