Commit | Line | Data |
---|---|---|
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 | ||
26 | package uk.org.distorted.tripe; package object sys { | |
27 | ||
28 | /*----- Imports -----------------------------------------------------------*/ | |
29 | ||
30 | import scala.collection.mutable.HashSet; | |
31 | ||
25c35469 MW |
32 | import java.io.{Closeable, File}; |
33 | import java.nio.{ByteBuffer, CharBuffer}; | |
34 | import java.nio.charset.Charset; | |
35 | import java.util.Date; | |
8eabb4ff | 36 | |
25c35469 MW |
37 | /*----- Some magic for C strings ------------------------------------------*/ |
38 | ||
39 | type 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 | ||
47 | class InvalidCStringException(msg: String) extends Exception(msg); | |
48 | ||
49 | object 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 | } | |
121 | import StringImplicits._; | |
122 | ||
123 | /*----- Main code ---------------------------------------------------------*/ | |
124 | ||
125 | /* Import the native code library. */ | |
126 | System.loadLibrary("toy"); | |
127 | ||
128 | /* Exception indicating that a wrapped native object has been clobbered. */ | |
129 | class NativeObjectTypeException(msg: String) extends RuntimeException(msg); | |
130 | type Wrapper = Array[Byte]; | |
8eabb4ff MW |
131 | |
132 | /*----- Error codes -------------------------------------------------------*/ | |
133 | ||
25c35469 MW |
134 | protected 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 | 138 | object 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 | } | |
336 | import Errno.{Value => _, _}; | |
337 | ||
338 | object 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 | ||
347 | class 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); | |
359 | def unlink(path: String) { unlink(path.toCString); } | |
360 | @native protected def rmdir(path: CString); | |
361 | def rmdir(path: String) { rmdir(path.toCString); } | |
362 | @native protected def mkdir(path: CString, mode: Int); | |
363 | def mkdir(path: String, mode: Int) { mkdir(path.toCString, mode); } | |
364 | @native protected def mkfile(path: CString, mode: Int); | |
365 | def mkfile(path: String, mode: Int) { mkfile(path.toCString, mode); } | |
366 | @native protected def rename(from: CString, to: CString); | |
367 | def 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 | */ | |
375 | val S_IFMT = 0xf000; | |
376 | val S_IFIFO = 0x1000; | |
377 | val S_IFCHR = 0x2000; | |
378 | val S_IFDIR = 0x4000; | |
379 | val S_IFBLK = 0x6000; | |
380 | val S_IFREG = 0x8000; | |
381 | val S_IFLNK = 0xa000; | |
382 | val S_IFSOCK = 0xc000; | |
383 | ||
384 | @native protected def stat(path: CString): sys.FileInfo; | |
385 | def stat(path: String): sys.FileInfo = stat(path.toCString); | |
386 | @native protected def lstat(path: CString): sys.FileInfo; | |
387 | def lstat(path: String): sys.FileInfo = lstat(path.toCString); | |
388 | ||
389 | object FileInfo extends Enumeration { | |
390 | val FIFO, CHR, DIR, BLK, REG, LNK, SOCK, UNK = Value; | |
391 | type Type = Value; | |
392 | } | |
393 | import FileInfo._; | |
394 | ||
395 | ||
396 | class 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 | ||
443 | protected 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 | ||
460 | class 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 | ||
465 | class 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 | ||
474 | val LKF_EXCL = 1; | |
475 | val LKF_WAIT = 2; | |
476 | @native protected def lock(path: CString, flags: Int): Wrapper; | |
477 | @native protected def unlock(lock: Wrapper); | |
478 | ||
479 | class 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 | ||
490 | object 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 | } | |
556 | import FileImplicits._; | |
557 | ||
558 | /*----- Miscellaneous file hacks ------------------------------------------*/ | |
8eabb4ff MW |
559 | |
560 | def 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 | ||
609 | val CF_CLOSERD = 1; | |
610 | val CF_CLOSEWR = 2; | |
611 | val 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 | ||
619 | class 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 | } |