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 | ||
c8292b34 | 30 | import scala.collection.convert.decorateAsJava._; |
3bb2303d | 31 | import scala.collection.mutable.{HashMap, HashSet}; |
8eabb4ff | 32 | |
c8292b34 MW |
33 | import java.io.{BufferedReader, BufferedWriter, Closeable, File, |
34 | FileDescriptor, FileInputStream, FileOutputStream, | |
ad64fbfa | 35 | FileReader, FileWriter, |
c8292b34 MW |
36 | InputStream, InputStreamReader, |
37 | OutputStream, OutputStreamWriter}; | |
25c35469 MW |
38 | import java.nio.{ByteBuffer, CharBuffer}; |
39 | import java.nio.charset.Charset; | |
40 | import java.util.Date; | |
8eabb4ff | 41 | |
0157de02 MW |
42 | import Implicits.truish; |
43 | ||
25c35469 MW |
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 | ||
c8292b34 | 68 | loop[CString] { exit => |
25c35469 MW |
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; | |
c8292b34 | 96 | exit(v); |
25c35469 MW |
97 | } |
98 | } | |
25c35469 MW |
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. */ | |
3bb2303d | 130 | System.loadLibrary("tripe"); |
25c35469 | 131 | |
c8292b34 MW |
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 | */ | |
25c35469 | 138 | type Wrapper = Array[Byte]; |
c8292b34 | 139 | class NativeObjectTypeException(msg: String) extends RuntimeException(msg); |
8eabb4ff MW |
140 | |
141 | /*----- Error codes -------------------------------------------------------*/ | |
142 | ||
c8292b34 | 143 | /* Machinery for collecting error information from C. */ |
25c35469 MW |
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 | ||
8eabb4ff | 148 | object Errno extends Enumeration { |
c8292b34 MW |
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 | |
8eabb4ff | 171 | val b = Map.newBuilder[String, Int]; |
25c35469 | 172 | for (ErrorEntry(tag, err) <- errtab) b += tag -> err; |
8eabb4ff MW |
173 | b.result |
174 | } | |
8eabb4ff | 175 | |
c8292b34 MW |
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 | ||
04a5abae MW |
181 | class Val private[Errno](tag: String, val code: Int, id: Int) |
182 | extends super.Val(id, tag) { | |
c8292b34 MW |
183 | /* Our augmented error type. */ |
184 | ||
25c35469 | 185 | def message: String = strerror(code).toJString; |
8eabb4ff | 186 | } |
c8292b34 | 187 | private class UnknownError(code: Int) |
04a5abae | 188 | extends Val("<unknown>", code, code); |
8eabb4ff | 189 | |
04a5abae | 190 | private def err(tag: String, code: Int): Val = { |
c8292b34 MW |
191 | /* Construct an error symbol given its tag string and a code number. */ |
192 | ||
04a5abae MW |
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) } | |
c8292b34 | 196 | } |
04a5abae | 197 | private def err(tag: String): Val = |
c8292b34 MW |
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) | |
8eabb4ff | 203 | } |
8eabb4ff | 204 | |
c8292b34 | 205 | val OK = err("OK", 0); // `errno' zero is a real thing |
8eabb4ff MW |
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 | } | |
04a5abae | 383 | import Errno.{Val => Errno, EEXIST, EISDIR, ENOENT, ENOTDIR}; |
8eabb4ff MW |
384 | |
385 | object SystemError { | |
c8292b34 MW |
386 | /* Pattern matching for `SystemError', below. */ |
387 | ||
388 | def apply(err: Errno, what: String): SystemError = | |
8eabb4ff | 389 | new SystemError(err, what); |
c8292b34 | 390 | def unapply(e: Exception): Option[(Errno, String)] = e match { |
8eabb4ff MW |
391 | case e: SystemError => Some((e.err, e.what)) |
392 | case _ => None | |
393 | } | |
394 | } | |
c8292b34 MW |
395 | class SystemError (val err: Errno, val what: String) extends Exception { |
396 | /* An error from a syscall or similar, usually from native code. */ | |
8eabb4ff | 397 | |
c8292b34 | 398 | /* A constructor which takes an error number, for easier access from C. */ |
25c35469 | 399 | private def this(err: Int, what: CString) |
c8292b34 MW |
400 | { this(Errno.byid(err).asInstanceOf[Errno], what.toJString); } |
401 | ||
8eabb4ff MW |
402 | override def getMessage(): String = s"$what: ${err.message}"; |
403 | } | |
404 | ||
25c35469 MW |
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); } | |
ad64fbfa MW |
413 | @native protected def chmod(path: CString, mode: Int); |
414 | def chmod(path: String, mode: Int) { chmod(path.toCString, mode); } | |
25c35469 MW |
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 | ||
c8292b34 MW |
421 | @native def fdint(fd: FileDescriptor): Int; |
422 | @native def newfd(fd: Int): FileDescriptor; | |
423 | @native def isatty(fd: FileDescriptor): Boolean; | |
424 | ||
25c35469 MW |
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 | */ | |
c8292b34 MW |
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. */ | |
25c35469 MW |
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 { | |
a5ec891a MW |
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; | |
25c35469 MW |
451 | type Type = Value; |
452 | } | |
453 | import FileInfo._; | |
454 | ||
25c35469 MW |
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) { | |
c8292b34 MW |
463 | /* Information about a file. This is constructed directly from native |
464 | * code. | |
465 | */ | |
466 | ||
25c35469 MW |
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) { | |
c8292b34 MW |
472 | /* Lightly cook the values from the underlying `struct stat'. */ |
473 | ||
25c35469 MW |
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 | ||
c8292b34 | 479 | /* Return the file permissions only. */ |
25c35469 MW |
480 | def perms: Int = mode&0xfff; |
481 | ||
c8292b34 | 482 | /* Return the filetype, as a `FileInfo.Type'. */ |
25c35469 MW |
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() { | |
c8292b34 | 495 | /* Insist that you only ask for `rdev' fields on actual device nodes. */ |
25c35469 | 496 | ftype match { |
c8292b34 | 497 | case CHR | BLK => ok; |
25c35469 MW |
498 | case _ => throw new IllegalArgumentException("Object is not a device"); |
499 | } | |
500 | } | |
c8292b34 MW |
501 | |
502 | /* Query the device-node numbers. */ | |
25c35469 MW |
503 | def rdevMajor: Int = { mustBeDevice(); _rdevMajor } |
504 | def rdevMinor: Int = { mustBeDevice(); _rdevMinor } | |
505 | } | |
506 | ||
507 | /*----- Listing directories -----------------------------------------------*/ | |
508 | ||
c8292b34 | 509 | /* Primitive operations. */ |
25c35469 MW |
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 { | |
c8292b34 MW |
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. */ | |
25c35469 MW |
525 | def this(path: String) { this(path.toCString); } |
526 | def this(dir: File) { this(dir.getPath); } | |
c8292b34 MW |
527 | |
528 | /* Cleaning up after ourselves. */ | |
25c35469 MW |
529 | override def close() { closedir(cpath, dir); } |
530 | override protected def finalize() { super.finalize(); close(); } | |
c8292b34 MW |
531 | |
532 | /* Subclass responsibility. */ | |
25c35469 | 533 | protected def mangle(file: String): T; |
c8292b34 MW |
534 | |
535 | /* Main machinery. */ | |
536 | private[this] val dir = opendir(cpath); | |
25c35469 MW |
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) { | |
c8292b34 MW |
547 | /* Iterator over the basenames of files in a directory. */ |
548 | ||
25c35469 | 549 | def this(dir: File) { this(dir.getPath); } |
c8292b34 | 550 | |
25c35469 MW |
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) { | |
c8292b34 MW |
556 | /* Iterator over full `File' objects in a directory. */ |
557 | ||
25c35469 MW |
558 | def this(dir: File) { this(dir, dir.getPath.toCString); } |
559 | def this(path: String) { this(new File(path), path.toCString); } | |
c8292b34 | 560 | |
25c35469 MW |
561 | override protected def mangle(file: String): File = new File(dir, file); |
562 | } | |
563 | ||
564 | /*----- File locking ------------------------------------------------------*/ | |
565 | ||
c8292b34 MW |
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; | |
25c35469 MW |
572 | @native protected def unlock(lock: Wrapper); |
573 | ||
574 | class FileLock(path: String, flags: Int) extends Closeable { | |
c8292b34 MW |
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 | */ | |
25c35469 | 580 | def this(file: File, flags: Int) { this(file.getPath, flags); } |
c8292b34 MW |
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. */ | |
25c35469 | 585 | private[this] val lk = lock(path.toCString, flags); |
c8292b34 MW |
586 | |
587 | /* Making sure things get cleaned up. */ | |
25c35469 MW |
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) { | |
c8292b34 MW |
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 | */ | |
25c35469 | 603 | |
c8292b34 MW |
604 | /* Constructing names of files in a directory. Honestly, I'm surprised |
605 | * there isn't a method for this already. | |
606 | */ | |
04a5abae | 607 | def /(sub: String): File = new File(file, sub); |
c8292b34 MW |
608 | |
609 | /* Simple file operations. */ | |
25c35469 MW |
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); } | |
ad64fbfa | 614 | def chmod_!(mode: Int) { chmod(file.getPath, mode); } |
25c35469 MW |
615 | def mkfile_!(mode: Int) { mkfile(file.getPath, mode); } |
616 | def mkfile_!() { mkfile_!(0x1b6); } | |
c8292b34 | 617 | def rename_!(to: File) { rename(file.getPath, to.getPath); } |
25c35469 | 618 | |
c8292b34 | 619 | /* Listing directories. */ |
25c35469 MW |
620 | def withFilesIterator[T](body: DirFilesIterator => T): T = { |
621 | val iter = new DirFilesIterator(file.getPath); | |
622 | try { body(iter) } finally { iter.close(); } | |
623 | } | |
c8292b34 | 624 | def foreachFile(fn: File => Unit) { withFilesIterator(_.foreach(fn)) } |
25c35469 MW |
625 | def files_! : Seq[File] = withFilesIterator { _.toSeq }; |
626 | ||
c8292b34 | 627 | /* Low-level lFile information. */ |
25c35469 MW |
628 | def stat_! : FileInfo = stat(file.getPath); |
629 | def lstat_! : FileInfo = lstat(file.getPath); | |
630 | ||
c8292b34 | 631 | /* Specific file-status queries. */ |
25c35469 MW |
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 | ||
a5ec891a | 649 | /* Slightly more cooked file operations. */ |
25c35469 | 650 | def remove_!() { |
c8292b34 | 651 | /* Delete a file, or directory, whatever it is. */ |
a5ec891a MW |
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; } | |
25c35469 MW |
658 | } |
659 | } | |
660 | ||
661 | def rmTree() { | |
c8292b34 | 662 | /* Delete a thing recursively. */ |
25c35469 | 663 | def walk(f: File) { |
c8292b34 | 664 | if (f.isdir_!) f.foreachFile(walk _); |
25c35469 MW |
665 | f.remove_!(); |
666 | } | |
667 | walk(file); | |
668 | } | |
669 | ||
a5ec891a MW |
670 | def mkdirNew_!() { |
671 | /* Make a directory if there's nothing there already. */ | |
672 | try { mkdir_!(); } | |
673 | catch { case SystemError(EEXIST, _) => ok; } | |
674 | } | |
675 | ||
c8292b34 MW |
676 | /* File locking. */ |
677 | def lock_!(flags: Int): FileLock = new FileLock(file.getPath, flags); | |
678 | def lock_!(): FileLock = lock_!(LKF_EXCL | 0x1b6); | |
25c35469 | 679 | def withLock[T](flags: Int)(body: => T): T = { |
c8292b34 | 680 | val lk = lock_!(flags); |
25c35469 MW |
681 | try { body } finally { lk.close(); } |
682 | } | |
c8292b34 MW |
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); | |
ad64fbfa MW |
688 | def reader(): BufferedReader = new BufferedReader(new FileReader(file)); |
689 | def writer(): BufferedWriter = new BufferedWriter(new FileWriter(file)); | |
c8292b34 MW |
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 | } | |
ad64fbfa MW |
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(); } | |
c8292b34 | 708 | } |
25c35469 MW |
709 | } |
710 | } | |
711 | import FileImplicits._; | |
712 | ||
713 | /*----- Miscellaneous file hacks ------------------------------------------*/ | |
8eabb4ff MW |
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 | ||
c8292b34 | 721 | loop[File] { exit => |
8eabb4ff MW |
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 | */ | |
04a5abae | 753 | val f = d/b.result; b.clear(); |
c8292b34 MW |
754 | try { f.mkfile_!(); exit(f); } |
755 | catch { case SystemError(EEXIST, _) => ok; } | |
8eabb4ff | 756 | } |
c8292b34 MW |
757 | } |
758 | ||
759 | /*----- Running a command -------------------------------------------------*/ | |
8eabb4ff | 760 | |
c8292b34 MW |
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. */ | |
9190adc6 | 782 | val pb = new ProcessBuilder(cmd.asJava); |
c8292b34 | 783 | val kid = pb.start(); clean { kid.destroy(); } |
9190adc6 | 784 | kid.getOutputStream.close(); |
c8292b34 MW |
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; | |
0157de02 | 806 | if (rc) throw new SubprocessFailed(cmd, rc, berr.result); |
c8292b34 MW |
807 | |
808 | /* We're all done. */ | |
809 | return (bout.result, berr.result); | |
810 | } | |
8eabb4ff MW |
811 | } |
812 | ||
04a5abae MW |
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 | ||
3bb2303d MW |
820 | @native protected def make_trigger(): Wrapper; |
821 | @native protected def destroy_trigger(trig: Wrapper); | |
822 | @native protected def reset_trigger(trig: Wrapper); | |
04a5abae MW |
823 | @native protected def trigger(trig: Wrapper); |
824 | ||
825 | private def getTrigger(): Wrapper = { | |
826 | triggerLock synchronized { | |
0157de02 | 827 | if (!nTriggers) |
3bb2303d | 828 | make_trigger() |
04a5abae MW |
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) { | |
3bb2303d | 839 | reset_trigger(trig); |
04a5abae MW |
840 | triggerLock synchronized { |
841 | if (nTriggers >= maxTriggers) | |
3bb2303d | 842 | destroy_trigger(trig); |
04a5abae MW |
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 | ||
3bb2303d | 869 | /*----- Glue for the VPN server -------------------------------------------*/ |
25c35469 | 870 | |
3bb2303d MW |
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; | |
c8292b34 | 875 | |
3bb2303d MW |
876 | /* Exceptions. */ |
877 | class NameResolutionException(msg: String) extends Exception(msg); | |
878 | class InitializationException(msg: String) extends Exception(msg); | |
25c35469 | 879 | |
3bb2303d MW |
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 | } | |
c8292b34 | 911 | |
3bb2303d MW |
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; | |
8eabb4ff | 918 | } |
3bb2303d MW |
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 | } | |
25c35469 | 925 | |
3bb2303d MW |
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() { } | |
8eabb4ff | 932 | } |
8eabb4ff | 933 | |
25c35469 MW |
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. */ | |
8eabb4ff MW |
938 | |
939 | /*----- That's all, folks -------------------------------------------------*/ | |
940 | ||
941 | } |