rough work in progress; may not build
[tripe-android] / jni.c
CommitLineData
8eabb4ff
MW
1/* -*-c-*-
2 *
3 * Native-code portions of the project
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/*----- Header files ------------------------------------------------------*/
27
28#define _FILE_OFFSET_BITS 64
29
3a2f1a4b 30#include <assert.h>
8eabb4ff 31#include <ctype.h>
3a2f1a4b
MW
32#include <errno.h>
33#include <inttypes.h>
8eabb4ff 34#include <stdarg.h>
3a2f1a4b
MW
35#include <stdio.h>
36#include <stdlib.h>
37#include <string.h>
38
39#include <jni.h>
40
41#include <sys/types.h>
42#include <sys/socket.h>
8eabb4ff
MW
43#include <sys/stat.h>
44#include <sys/sysmacros.h>
3a2f1a4b 45#include <sys/un.h>
8eabb4ff 46#include <fcntl.h>
3a2f1a4b 47#include <unistd.h>
8eabb4ff
MW
48#include <dirent.h>
49
50#include <mLib/align.h>
51#include <mLib/bits.h>
52#include <mLib/dstr.h>
53#include <mLib/macros.h>
54
55#include <catacomb/ghash.h>
3a2f1a4b
MW
56
57#undef sun
58
8eabb4ff
MW
59/*----- Magic class names and similar -------------------------------------*/
60
61/* The name decoration is horrific. Hide it. */
25c35469 62#define JNIFUNC(f) Java_uk_org_distorted_tripe_sys_package_00024_##f
8eabb4ff
MW
63
64/* The little class for bundling up error codes. */
25c35469 65#define ERRENTRY "uk/org/distorted/tripe/sys/package$ErrorEntry"
8eabb4ff
MW
66
67/* The `stat' class. */
25c35469 68#define STAT "uk/org/distorted/tripe/sys/package$FileInfo"
8eabb4ff
MW
69
70/* Exception class names. */
71#define NULLERR "java/lang/NullPointerException"
25c35469 72#define TYPEERR "uk/org/distorted/tripe/sys/package$NativeObjectTypeException"
8eabb4ff
MW
73#define SYSERR "uk/org/distorted/tripe/sys/package$SystemError"
74#define ARGERR "java/lang/IllegalArgumentException"
75#define BOUNDSERR "java/lang/IndexOutOfBoundsException"
76
77/*----- Miscellaneous utilities -------------------------------------------*/
78
79static void put_cstring(JNIEnv *jni, jbyteArray v, const char *p)
80 { if (p) (*jni)->ReleaseByteArrayElements(jni, v, (jbyte *)p, JNI_ABORT); }
81
82static void vexcept(JNIEnv *jni, const char *clsname,
25c35469 83 const char *msg, va_list *ap)
8eabb4ff
MW
84{
85 jclass cls;
86 int rc;
87 dstr d = DSTR_INIT;
88
89 cls = (*jni)->FindClass(jni, clsname); assert(cls);
90 if (!msg)
91 rc = (*jni)->ThrowNew(jni, cls, 0);
92 else {
25c35469 93 dstr_vputf(&d, msg, ap);
8eabb4ff
MW
94 rc = (*jni)->ThrowNew(jni, cls, d.buf);
95 assert(!rc);
96 dstr_destroy(&d);
97 }
98 assert(!rc);
99}
100
101static void except(JNIEnv *jni, const char *clsname, const char *msg, ...)
102{
103 va_list ap;
104
105 va_start(ap, msg);
25c35469 106 vexcept(jni, clsname, msg, &ap);
8eabb4ff
MW
107 va_end(ap);
108}
109
110#ifdef DEBUG
111static void dump_bytes(const void *p, size_t n, size_t o)
112{
113 const unsigned char *q = p;
114 size_t i;
115
116 if (!n) return;
117 for (;;) {
118 fprintf(stderr, ";; %08zx\n", o);
119 for (i = 0; i < 8; i++)
120 if (i < n) fprintf(stderr, "%02x ", q[i]);
121 else fprintf(stderr, "** ");
122 fprintf(stderr, ": ");
123 for (i = 0; i < 8; i++)
124 fputc(i >= n ? '*' : isprint(q[i]) ? q[i] : '.', stderr);
125 fputc('\n', stderr);
126 if (n <= 8) break;
127 q += 8; n -= 8;
128 }
129}
130
131static void dump_byte_array(JNIEnv *jni, const char *what, jbyteArray v)
132{
133 jsize n;
134 jbyte *p;
135
136 fprintf(stderr, ";; %s\n", what);
137 if (!v) { fprintf(stderr, ";; <null>\n"); return; }
138 n = (*jni)->GetArrayLength(jni, v);
139 p = (*jni)->GetByteArrayElements(jni, v, 0);
140 dump_bytes(p, n, 0);
141 (*jni)->ReleaseByteArrayElements(jni, v, p, JNI_ABORT);
142}
143#endif
144
145static jbyteArray wrap_cstring(JNIEnv *jni, const char *p)
146{
147 size_t n;
148 jbyteArray v;
149 jbyte *q;
150
151 if (!p) return (0);
152 n = strlen(p) + 1;
153 v = (*jni)->NewByteArray(jni, n); if (!v) return (0);
154 q = (*jni)->GetByteArrayElements(jni, v, 0); if (!q) return (0);
155 memcpy(q, p, n);
156 (*jni)->ReleaseByteArrayElements(jni, v, q, 0);
157 return (v);
158}
159
160static const char *get_cstring(JNIEnv *jni, jbyteArray v)
161{
162 if (!v) { except(jni, NULLERR, 0); return (0); }
163 return ((const char *)(*jni)->GetByteArrayElements(jni, v, 0));
164}
165
166static void vexcept_syserror(JNIEnv *jni, const char *clsname,
25c35469 167 int err, const char *msg, va_list *ap)
8eabb4ff
MW
168{
169 jclass cls;
170 int rc;
171 dstr d = DSTR_INIT;
172 jbyteArray msgstr;
173 jthrowable e;
174 jmethodID init;
175
176 cls = (*jni)->FindClass(jni, clsname); assert(cls);
177 init = (*jni)->GetMethodID(jni, cls, "<init>", "(I[B)V"); assert(init);
25c35469 178 dstr_vputf(&d, msg, ap);
8eabb4ff
MW
179 msgstr = wrap_cstring(jni, d.buf); assert(msgstr);
180 dstr_destroy(&d);
181 e = (*jni)->NewObject(jni, cls, init, err, msgstr); assert(e);
182 rc = (*jni)->Throw(jni, e); assert(!rc);
183}
184
185static void except_syserror(JNIEnv *jni, const char *clsname,
186 int err, const char *msg, ...)
187{
188 va_list ap;
189
190 va_start(ap, msg);
25c35469 191 vexcept_syserror(jni, clsname, err, msg, &ap);
8eabb4ff
MW
192 va_end(ap);
193}
194
195/*----- Wrapping native types ---------------------------------------------*/
196
197/* There's no way defined in the JNI to stash a C pointer in a Java object.
198 * It seems that the usual approach is to cast to `jlong', but this is
199 * clearly unsatisfactory. Instead, we store structures as Java byte arrays,
200 * with a 32-bit tag on the front.
201 */
3a2f1a4b
MW
202
203struct native_type {
204 const char *name;
205 size_t sz;
8eabb4ff 206 uint32 tag;
3a2f1a4b
MW
207};
208
8eabb4ff 209typedef jbyteArray wrapper;
3a2f1a4b 210
8eabb4ff
MW
211struct native_base {
212 uint32 tag;
3a2f1a4b
MW
213};
214
8eabb4ff
MW
215static int unwrap(JNIEnv *jni, void *p,
216 const struct native_type *ty, wrapper w)
217{
218 jbyte *q;
219 jclass cls;
220 struct native_base *b = p;
221 jsize n;
222
223 if (!w) { except(jni, NULLERR, 0); return (-1); }
224 cls = (*jni)->FindClass(jni, "[B"); assert(cls);
225 if (!(*jni)->IsInstanceOf(jni, w, cls)) {
226 except(jni, TYPEERR,
227 "corrupted native object wrapper: expected a byte array");
228 return (-1);
229 }
230 n = (*jni)->GetArrayLength(jni, w);
231 if (n != ty->sz) {
232 except(jni, TYPEERR,
233 "corrupted native object wrapper: wrong size for `%s'",
234 ty->name);
235 return (-1);
236 }
237 q = (*jni)->GetByteArrayElements(jni, w, 0); if (!q) return (-1);
238 memcpy(b, q, ty->sz);
239 (*jni)->ReleaseByteArrayElements(jni, w, q, JNI_ABORT);
240 if (b->tag != ty->tag) {
241 except(jni, TYPEERR,
242 "corrupted native object wrapper: expected tag for `%s'",
243 ty->name);
244 return (-1);
245 }
246 return (0);
247}
248
249static int update_wrapper(JNIEnv *jni, const struct native_type *ty,
250 wrapper w, const void *p)
251{
252 jbyte *q;
253
254 q = (*jni)->GetByteArrayElements(jni, w, 0); if (!q) return (-1);
255 memcpy(q, p, ty->sz);
256 (*jni)->ReleaseByteArrayElements(jni, w, q, 0);
257 return (0);
258}
259
260static wrapper wrap(JNIEnv *jni, const struct native_type *ty, const void *p)
261{
262 wrapper w;
263
264 w = (*jni)->NewByteArray(jni, ty->sz); if (!w) return (0);
265 if (update_wrapper(jni, ty, w, p)) return (0);
266 return (w);
267}
268
269#define INIT_NATIVE(type, p) do (p)->_base.tag = type##_type.tag; while (0)
270
271/*----- Crypto information ------------------------------------------------*/
272
273JNIEXPORT jint JNICALL JNIFUNC(hashsz)(JNIEnv *jni, jobject cls,
274 jstring hnamestr)
275{
276 jint rc = -1;
277 const char *hname;
278 const gchash *hc;
279
280 hname = (*jni)->GetStringUTFChars(jni, hnamestr, 0);
281 if (!hname) goto end;
282 hc = ghash_byname(hname); if (!hc) goto end;
283 rc = hc->hashsz;
284
285end:
286 if (hname) (*jni)->ReleaseStringUTFChars(jni, hnamestr, hname);
287 return (rc);
288}
289
290/*----- System errors -----------------------------------------------------*/
291
292static const struct errtab { const char *tag; int err; } errtab[] = {
293 /*
294 ;;; The errno name table is very boring to type. To make life less
295 ;;; awful, put the errno names in this list and evaluate the code to
296 ;;; get Emacs to regenerate it.
297
298 (let ((errors '(EPERM ENOENT ESRCH EINTR EIO ENXIO E2BIG ENOEXEC EBADF
299 ECHILD EAGAIN ENOMEM EACCES EFAULT ENOTBLK EBUSY EEXIST
300 EXDEV ENODEV ENOTDIR EISDIR EINVAL ENFILE EMFILE ENOTTY
301 ETXTBSY EFBIG ENOSPC ESPIPE EROFS EMLINK EPIPE EDOM
302 ERANGE
303
304 EDEADLK ENAMETOOLONG ENOLCK ENOSYS ENOTEMPTY ELOOP
305 EWOULDBLOCK ENOMSG EIDRM ECHRNG EL2NSYNC EL3HLT EL3RST
306 ELNRNG EUNATCH ENOCSI EL2HLT EBADE EBADR EXFULL ENOANO
307 EBADRQC EBADSLT EDEADLOCK EBFONT ENOSTR ENODATA ETIME
308 ENOSR ENONET ENOPKG EREMOTE ENOLINK EADV ESRMNT ECOMM
309 EPROTO EMULTIHOP EDOTDOT EBADMSG EOVERFLOW ENOTUNIQ
310 EBADFD EREMCHG ELIBACC ELIBBAD ELIBSCN ELIBMAX ELIBEXEC
311 EILSEQ ERESTART ESTRPIPE EUSERS ENOTSOCK EDESTADDRREQ
312 EMSGSIZE EPROTOTYPE ENOPROTOOPT EPROTONOSUPPORT
313 ESOCKTNOSUPPORT EOPNOTSUPP EPFNOSUPPORT EAFNOSUPPORT
314 EADDRINUSE EADDRNOTAVAIL ENETDOWN ENETUNREACH ENETRESET
315 ECONNABORTED ECONNRESET ENOBUFS EISCONN ENOTCONN
316 ESHUTDOWN ETOOMANYREFS ETIMEDOUT ECONNREFUSED EHOSTDOWN
317 EHOSTUNREACH EALREADY EINPROGRESS ESTALE EUCLEAN ENOTNAM
318 ENAVAIL EISNAM EREMOTEIO EDQUOT ENOMEDIUM EMEDIUMTYPE
319 ECANCELED ENOKEY EKEYEXPIRED EKEYREVOKED EKEYREJECTED
320 EOWNERDEAD ENOTRECOVERABLE ERFKILL EHWPOISON)))
321 (save-excursion
c8292b34
MW
322 (goto-char (point-min))
323 (search-forward (concat "***" "BEGIN errtab" "***"))
324 (beginning-of-line 2)
325 (delete-region (point)
326 (progn
327 (search-forward "***END***")
328 (beginning-of-line)
329 (point)))
330 (dolist (err errors)
331 (insert (format "#ifdef %s\n { \"%s\", %s },\n#endif\n"
332 err err err)))))
8eabb4ff
MW
333 */
334 /***BEGIN errtab***/
335#ifdef EPERM
336 { "EPERM", EPERM },
337#endif
338#ifdef ENOENT
339 { "ENOENT", ENOENT },
340#endif
341#ifdef ESRCH
342 { "ESRCH", ESRCH },
343#endif
344#ifdef EINTR
345 { "EINTR", EINTR },
346#endif
347#ifdef EIO
348 { "EIO", EIO },
349#endif
350#ifdef ENXIO
351 { "ENXIO", ENXIO },
352#endif
353#ifdef E2BIG
354 { "E2BIG", E2BIG },
355#endif
356#ifdef ENOEXEC
357 { "ENOEXEC", ENOEXEC },
358#endif
359#ifdef EBADF
360 { "EBADF", EBADF },
361#endif
362#ifdef ECHILD
363 { "ECHILD", ECHILD },
364#endif
365#ifdef EAGAIN
366 { "EAGAIN", EAGAIN },
367#endif
368#ifdef ENOMEM
369 { "ENOMEM", ENOMEM },
370#endif
371#ifdef EACCES
372 { "EACCES", EACCES },
373#endif
374#ifdef EFAULT
375 { "EFAULT", EFAULT },
376#endif
377#ifdef ENOTBLK
378 { "ENOTBLK", ENOTBLK },
379#endif
380#ifdef EBUSY
381 { "EBUSY", EBUSY },
382#endif
383#ifdef EEXIST
384 { "EEXIST", EEXIST },
385#endif
386#ifdef EXDEV
387 { "EXDEV", EXDEV },
388#endif
389#ifdef ENODEV
390 { "ENODEV", ENODEV },
391#endif
392#ifdef ENOTDIR
393 { "ENOTDIR", ENOTDIR },
394#endif
395#ifdef EISDIR
396 { "EISDIR", EISDIR },
397#endif
398#ifdef EINVAL
399 { "EINVAL", EINVAL },
400#endif
401#ifdef ENFILE
402 { "ENFILE", ENFILE },
403#endif
404#ifdef EMFILE
405 { "EMFILE", EMFILE },
406#endif
407#ifdef ENOTTY
408 { "ENOTTY", ENOTTY },
409#endif
410#ifdef ETXTBSY
411 { "ETXTBSY", ETXTBSY },
412#endif
413#ifdef EFBIG
414 { "EFBIG", EFBIG },
415#endif
416#ifdef ENOSPC
417 { "ENOSPC", ENOSPC },
418#endif
419#ifdef ESPIPE
420 { "ESPIPE", ESPIPE },
421#endif
422#ifdef EROFS
423 { "EROFS", EROFS },
424#endif
425#ifdef EMLINK
426 { "EMLINK", EMLINK },
427#endif
428#ifdef EPIPE
429 { "EPIPE", EPIPE },
430#endif
431#ifdef EDOM
432 { "EDOM", EDOM },
433#endif
434#ifdef ERANGE
435 { "ERANGE", ERANGE },
436#endif
437#ifdef EDEADLK
438 { "EDEADLK", EDEADLK },
439#endif
440#ifdef ENAMETOOLONG
441 { "ENAMETOOLONG", ENAMETOOLONG },
442#endif
443#ifdef ENOLCK
444 { "ENOLCK", ENOLCK },
445#endif
446#ifdef ENOSYS
447 { "ENOSYS", ENOSYS },
448#endif
449#ifdef ENOTEMPTY
450 { "ENOTEMPTY", ENOTEMPTY },
451#endif
452#ifdef ELOOP
453 { "ELOOP", ELOOP },
454#endif
455#ifdef EWOULDBLOCK
456 { "EWOULDBLOCK", EWOULDBLOCK },
457#endif
458#ifdef ENOMSG
459 { "ENOMSG", ENOMSG },
460#endif
461#ifdef EIDRM
462 { "EIDRM", EIDRM },
463#endif
464#ifdef ECHRNG
465 { "ECHRNG", ECHRNG },
466#endif
467#ifdef EL2NSYNC
468 { "EL2NSYNC", EL2NSYNC },
469#endif
470#ifdef EL3HLT
471 { "EL3HLT", EL3HLT },
472#endif
473#ifdef EL3RST
474 { "EL3RST", EL3RST },
475#endif
476#ifdef ELNRNG
477 { "ELNRNG", ELNRNG },
478#endif
479#ifdef EUNATCH
480 { "EUNATCH", EUNATCH },
481#endif
482#ifdef ENOCSI
483 { "ENOCSI", ENOCSI },
484#endif
485#ifdef EL2HLT
486 { "EL2HLT", EL2HLT },
487#endif
488#ifdef EBADE
489 { "EBADE", EBADE },
490#endif
491#ifdef EBADR
492 { "EBADR", EBADR },
493#endif
494#ifdef EXFULL
495 { "EXFULL", EXFULL },
496#endif
497#ifdef ENOANO
498 { "ENOANO", ENOANO },
499#endif
500#ifdef EBADRQC
501 { "EBADRQC", EBADRQC },
502#endif
503#ifdef EBADSLT
504 { "EBADSLT", EBADSLT },
505#endif
506#ifdef EDEADLOCK
507 { "EDEADLOCK", EDEADLOCK },
508#endif
509#ifdef EBFONT
510 { "EBFONT", EBFONT },
511#endif
512#ifdef ENOSTR
513 { "ENOSTR", ENOSTR },
514#endif
515#ifdef ENODATA
516 { "ENODATA", ENODATA },
517#endif
518#ifdef ETIME
519 { "ETIME", ETIME },
520#endif
521#ifdef ENOSR
522 { "ENOSR", ENOSR },
523#endif
524#ifdef ENONET
525 { "ENONET", ENONET },
526#endif
527#ifdef ENOPKG
528 { "ENOPKG", ENOPKG },
529#endif
530#ifdef EREMOTE
531 { "EREMOTE", EREMOTE },
532#endif
533#ifdef ENOLINK
534 { "ENOLINK", ENOLINK },
535#endif
536#ifdef EADV
537 { "EADV", EADV },
538#endif
539#ifdef ESRMNT
540 { "ESRMNT", ESRMNT },
541#endif
542#ifdef ECOMM
543 { "ECOMM", ECOMM },
544#endif
545#ifdef EPROTO
546 { "EPROTO", EPROTO },
547#endif
548#ifdef EMULTIHOP
549 { "EMULTIHOP", EMULTIHOP },
550#endif
551#ifdef EDOTDOT
552 { "EDOTDOT", EDOTDOT },
553#endif
554#ifdef EBADMSG
555 { "EBADMSG", EBADMSG },
556#endif
557#ifdef EOVERFLOW
558 { "EOVERFLOW", EOVERFLOW },
559#endif
560#ifdef ENOTUNIQ
561 { "ENOTUNIQ", ENOTUNIQ },
562#endif
563#ifdef EBADFD
564 { "EBADFD", EBADFD },
565#endif
566#ifdef EREMCHG
567 { "EREMCHG", EREMCHG },
568#endif
569#ifdef ELIBACC
570 { "ELIBACC", ELIBACC },
571#endif
572#ifdef ELIBBAD
573 { "ELIBBAD", ELIBBAD },
574#endif
575#ifdef ELIBSCN
576 { "ELIBSCN", ELIBSCN },
577#endif
578#ifdef ELIBMAX
579 { "ELIBMAX", ELIBMAX },
580#endif
581#ifdef ELIBEXEC
582 { "ELIBEXEC", ELIBEXEC },
583#endif
584#ifdef EILSEQ
585 { "EILSEQ", EILSEQ },
586#endif
587#ifdef ERESTART
588 { "ERESTART", ERESTART },
589#endif
590#ifdef ESTRPIPE
591 { "ESTRPIPE", ESTRPIPE },
592#endif
593#ifdef EUSERS
594 { "EUSERS", EUSERS },
595#endif
596#ifdef ENOTSOCK
597 { "ENOTSOCK", ENOTSOCK },
598#endif
599#ifdef EDESTADDRREQ
600 { "EDESTADDRREQ", EDESTADDRREQ },
601#endif
602#ifdef EMSGSIZE
603 { "EMSGSIZE", EMSGSIZE },
604#endif
605#ifdef EPROTOTYPE
606 { "EPROTOTYPE", EPROTOTYPE },
607#endif
608#ifdef ENOPROTOOPT
609 { "ENOPROTOOPT", ENOPROTOOPT },
610#endif
611#ifdef EPROTONOSUPPORT
612 { "EPROTONOSUPPORT", EPROTONOSUPPORT },
613#endif
614#ifdef ESOCKTNOSUPPORT
615 { "ESOCKTNOSUPPORT", ESOCKTNOSUPPORT },
616#endif
617#ifdef EOPNOTSUPP
618 { "EOPNOTSUPP", EOPNOTSUPP },
619#endif
620#ifdef EPFNOSUPPORT
621 { "EPFNOSUPPORT", EPFNOSUPPORT },
622#endif
623#ifdef EAFNOSUPPORT
624 { "EAFNOSUPPORT", EAFNOSUPPORT },
625#endif
626#ifdef EADDRINUSE
627 { "EADDRINUSE", EADDRINUSE },
628#endif
629#ifdef EADDRNOTAVAIL
630 { "EADDRNOTAVAIL", EADDRNOTAVAIL },
631#endif
632#ifdef ENETDOWN
633 { "ENETDOWN", ENETDOWN },
634#endif
635#ifdef ENETUNREACH
636 { "ENETUNREACH", ENETUNREACH },
637#endif
638#ifdef ENETRESET
639 { "ENETRESET", ENETRESET },
640#endif
641#ifdef ECONNABORTED
642 { "ECONNABORTED", ECONNABORTED },
643#endif
644#ifdef ECONNRESET
645 { "ECONNRESET", ECONNRESET },
646#endif
647#ifdef ENOBUFS
648 { "ENOBUFS", ENOBUFS },
649#endif
650#ifdef EISCONN
651 { "EISCONN", EISCONN },
652#endif
653#ifdef ENOTCONN
654 { "ENOTCONN", ENOTCONN },
655#endif
656#ifdef ESHUTDOWN
657 { "ESHUTDOWN", ESHUTDOWN },
658#endif
659#ifdef ETOOMANYREFS
660 { "ETOOMANYREFS", ETOOMANYREFS },
661#endif
662#ifdef ETIMEDOUT
663 { "ETIMEDOUT", ETIMEDOUT },
664#endif
665#ifdef ECONNREFUSED
666 { "ECONNREFUSED", ECONNREFUSED },
667#endif
668#ifdef EHOSTDOWN
669 { "EHOSTDOWN", EHOSTDOWN },
670#endif
671#ifdef EHOSTUNREACH
672 { "EHOSTUNREACH", EHOSTUNREACH },
673#endif
674#ifdef EALREADY
675 { "EALREADY", EALREADY },
676#endif
677#ifdef EINPROGRESS
678 { "EINPROGRESS", EINPROGRESS },
679#endif
680#ifdef ESTALE
681 { "ESTALE", ESTALE },
682#endif
683#ifdef EUCLEAN
684 { "EUCLEAN", EUCLEAN },
685#endif
686#ifdef ENOTNAM
687 { "ENOTNAM", ENOTNAM },
688#endif
689#ifdef ENAVAIL
690 { "ENAVAIL", ENAVAIL },
691#endif
692#ifdef EISNAM
693 { "EISNAM", EISNAM },
694#endif
695#ifdef EREMOTEIO
696 { "EREMOTEIO", EREMOTEIO },
697#endif
698#ifdef EDQUOT
699 { "EDQUOT", EDQUOT },
700#endif
701#ifdef ENOMEDIUM
702 { "ENOMEDIUM", ENOMEDIUM },
703#endif
704#ifdef EMEDIUMTYPE
705 { "EMEDIUMTYPE", EMEDIUMTYPE },
706#endif
707#ifdef ECANCELED
708 { "ECANCELED", ECANCELED },
709#endif
710#ifdef ENOKEY
711 { "ENOKEY", ENOKEY },
712#endif
713#ifdef EKEYEXPIRED
714 { "EKEYEXPIRED", EKEYEXPIRED },
715#endif
716#ifdef EKEYREVOKED
717 { "EKEYREVOKED", EKEYREVOKED },
718#endif
719#ifdef EKEYREJECTED
720 { "EKEYREJECTED", EKEYREJECTED },
721#endif
722#ifdef EOWNERDEAD
723 { "EOWNERDEAD", EOWNERDEAD },
724#endif
725#ifdef ENOTRECOVERABLE
726 { "ENOTRECOVERABLE", ENOTRECOVERABLE },
727#endif
728#ifdef ERFKILL
729 { "ERFKILL", ERFKILL },
730#endif
731#ifdef EHWPOISON
732 { "EHWPOISON", EHWPOISON },
733#endif
734 /***END***/
3a2f1a4b
MW
735};
736
8eabb4ff 737JNIEXPORT jobject JNIFUNC(errtab)(JNIEnv *jni, jobject cls)
3a2f1a4b 738{
8eabb4ff
MW
739 size_t i;
740 jclass eltcls;
741 jarray v;
742 jmethodID init;
743 jobject e;
3a2f1a4b 744
8eabb4ff
MW
745 eltcls =
746 (*jni)->FindClass(jni, ERRENTRY);
747 assert(eltcls);
748 v = (*jni)->NewObjectArray(jni, N(errtab), eltcls, 0); if (!v) return (0);
749 init = (*jni)->GetMethodID(jni, eltcls, "<init>",
750 "(Ljava/lang/String;I)V");
751 assert(init);
752
753 for (i = 0; i < N(errtab); i++) {
754 e = (*jni)->NewObject(jni, eltcls, init,
755 (*jni)->NewStringUTF(jni, errtab[i].tag),
756 errtab[i].err);
757 (*jni)->SetObjectArrayElement(jni, v, i, e);
758 }
759 return (v);
760}
761
762JNIEXPORT jobject JNIFUNC(strerror)(JNIEnv *jni, jobject cls, jint err)
763 { return (wrap_cstring(jni, strerror(err))); }
764
c8292b34
MW
765/*----- Messing with file descriptors -------------------------------------*/
766
767static void fdguts(JNIEnv *jni, jclass *cls, jfieldID *fid)
768{
769 *cls = (*jni)->FindClass(jni, "java/io/FileDescriptor"); assert(cls);
770 *fid = (*jni)->GetFieldID(jni, *cls, "fd", "I"); // OpenJDK
771 if (!*fid) *fid = (*jni)->GetFieldID(jni, *cls, "descriptor", "I"); // Android
772 assert(*fid);
773}
774
775static int fdint(JNIEnv *jni, jobject jfd)
776{
777 jclass cls;
778 jfieldID fid;
779
780 fdguts(jni, &cls, &fid);
781 return ((*jni)->GetIntField(jni, jfd, fid));
782}
783
784static jobject newfd(JNIEnv *jni, int fd)
785{
786 jobject jfd;
787 jclass cls;
788 jmethodID init;
789 jfieldID fid;
790
791 fdguts(jni, &cls, &fid);
792 init = (*jni)->GetMethodID(jni, cls, "<init>", "()V"); assert(init);
793 jfd = (*jni)->NewObject(jni, cls, init);
794 (*jni)->SetIntField(jni, jfd, fid, fd);
795 return (jfd);
796}
797
798JNIEXPORT jint JNIFUNC(fdint)(JNIEnv *jni, jobject cls, jobject jfd)
799 { return (fdint(jni, jfd)); }
800
801JNIEXPORT jobject JNIFUNC(newfd)(JNIEnv *jni, jobject cls, jint fd)
802 { return (newfd(jni, fd)); }
803
804JNIEXPORT jboolean JNIFUNC(isatty)(JNIEnv *jni, jobject cls, jobject jfd)
805 { return (isatty(fdint(jni, jfd))); }
806
8eabb4ff
MW
807/*----- Low-level file operations -----------------------------------------*/
808
809/* Java has these already, as methods on `java.io.File' objects. Alas, these
810 * methods are useless at reporting errors: they tend to return a `boolean'
811 * success/ fail indicator, and throw away any more detailed information.
812 * There's better functionality in `java.nio.file.Files', but that only turns
813 * up in Android API 26 (in 7.0 Nougat). There's `android.system.Os', which
814 * has a bunch of POSIX-shaped functions -- but they're only in Android API
815 * 21 (in 5.0 Lollipop), and there's nothing in the support library to help.
816 *
817 * So the other option is to implement them ourselves.
818 */
819
820JNIEXPORT void JNIFUNC(unlink)(JNIEnv *jni, jobject cls, jobject path)
821{
822 const char *pathstr = 0;
823
824 pathstr = get_cstring(jni, path); if (!pathstr) goto end;
825 if (unlink(pathstr)) {
826 except_syserror(jni, SYSERR, errno,
827 "failed to delete file `%s'", pathstr);
828 goto end;
829 }
830end:
831 put_cstring(jni, path, pathstr);
3a2f1a4b
MW
832}
833
8eabb4ff
MW
834JNIEXPORT void JNIFUNC(rmdir)(JNIEnv *jni, jobject cls, jobject path)
835{
836 const char *pathstr = 0;
3a2f1a4b 837
8eabb4ff
MW
838 pathstr = get_cstring(jni, path); if (!pathstr) goto end;
839 if (rmdir(pathstr)) {
840 except_syserror(jni, SYSERR, errno,
841 "failed to delete directory `%s'", pathstr);
842 goto end;
843 }
844end:
845 put_cstring(jni, path, pathstr);
846}
847
848JNIEXPORT void JNIFUNC(mkdir)(JNIEnv *jni, jobject cls,
849 jobject path, jint mode)
3a2f1a4b 850{
8eabb4ff
MW
851 const char *pathstr = 0;
852
853 pathstr = get_cstring(jni, path); if (!pathstr) goto end;
854 if (mkdir(pathstr, mode)) {
855 except_syserror(jni, SYSERR, errno,
856 "failed to create directory `%s'", pathstr);
857 goto end;
858 }
859end:
860 put_cstring(jni, path, pathstr);
3a2f1a4b
MW
861}
862
8eabb4ff
MW
863JNIEXPORT void JNIFUNC(mkfile)(JNIEnv *jni, jobject cls,
864 jobject path, jint mode)
3a2f1a4b 865{
8eabb4ff
MW
866 const char *pathstr = 0;
867 int fd = -1;
3a2f1a4b 868
8eabb4ff
MW
869 pathstr = get_cstring(jni, path); if (!pathstr) goto end;
870 fd = open(pathstr, O_WRONLY | O_CREAT | O_EXCL, mode);
871 if (fd < 0) {
872 except_syserror(jni, SYSERR, errno,
873 "failed to create fresh file `%s'", pathstr);
874 goto end;
3a2f1a4b 875 }
8eabb4ff
MW
876end:
877 if (fd != -1) close(fd);
878 put_cstring(jni, path, pathstr);
3a2f1a4b
MW
879}
880
8eabb4ff
MW
881JNIEXPORT void JNIFUNC(rename)(JNIEnv *jni, jobject cls,
882 jobject from, jobject to)
3a2f1a4b 883{
8eabb4ff
MW
884 const char *fromstr = 0, *tostr = 0;
885
886 fromstr = get_cstring(jni, from); if (!fromstr) goto end;
887 tostr = get_cstring(jni, to); if (!tostr) goto end;
888 if (rename(fromstr, tostr)) {
889 except_syserror(jni, SYSERR, errno,
890 "failed to rename `%s' as `%s'", fromstr, tostr);
891 goto end;
892 }
893end:
894 put_cstring(jni, from, fromstr);
895 put_cstring(jni, to, tostr);
3a2f1a4b
MW
896}
897
c8292b34
MW
898#define LKF_EXCL 0x1000u
899#define LKF_WAIT 0x2000u
8eabb4ff
MW
900struct lockf {
901 struct native_base _base;
902 int fd;
903};
904static struct native_type lockf_type =
905 { "lock", sizeof(struct lockf), 0xb2648926};
906JNIEXPORT wrapper JNIFUNC(lock)(JNIEnv *jni, jobject cls,
907 jobject path, jint flags)
908{
909 const char *pathstr = 0;
910 int fd = -1;
911 struct flock l;
912 struct lockf lk;
913 struct stat st0, st1;
914 int f;
915 wrapper r = 0;
916
917 pathstr = get_cstring(jni, path); if (!pathstr) goto end;
918
919again:
c8292b34 920 fd = open(pathstr, O_RDWR | O_CREAT, flags&07777); if (fd < 0) goto err;
8eabb4ff
MW
921 if (fstat(fd, &st0)) goto err;
922 f = fcntl(fd, F_GETFD); if (f < 0) goto err;
923 if (fcntl(fd, F_SETFD, f | FD_CLOEXEC)) goto err;
924 l.l_type = (flags&LKF_EXCL) ? F_WRLCK : F_RDLCK;
925 l.l_whence = SEEK_SET;
926 l.l_start = 0;
927 l.l_len = 0;
928 if (fcntl(fd, (flags&LKF_WAIT) ? F_SETLKW : F_SETLK, &l)) goto err;
929 if (stat(pathstr, &st1))
930 { if (errno == ENOENT) goto again; else goto err; }
931 if (st0.st_dev != st1.st_dev || st0.st_ino != st1.st_ino)
932 { close(fd); fd = -1; goto again; }
933
934 INIT_NATIVE(lockf, &lk); lk.fd = fd; fd = -1;
935 r = wrap(jni, &lockf_type, &lk);
936 goto end;
937
938err:
939 except_syserror(jni, SYSERR, errno, "failed to lock file `%s'", pathstr);
940end:
941 if (fd != -1) close(fd);
942 put_cstring(jni, path, pathstr);
943 return (r);
944}
945
946JNIEXPORT void JNIFUNC(unlock)(JNIEnv *jni, jobject cls, wrapper wlk)
947{
948 struct lockf lk;
949 struct flock l;
950 int rc;
951
952 if (unwrap(jni, &lk, &lockf_type, wlk)) goto end;
953 if (lk.fd == -1) goto end;
954 l.l_type = F_UNLCK;
955 l.l_whence = SEEK_SET;
956 l.l_start = 0;
957 l.l_len = 0;
958 if (fcntl(lk.fd, F_SETLK, &l)) goto end;
959 close(lk.fd); lk.fd = -1;
960 rc = update_wrapper(jni, &lockf_type, wlk, &lk); assert(!rc);
961end:;
962}
963
964static jlong xlttimespec(const struct timespec *ts)
965 { return (1000*(jlong)ts->tv_sec + ts->tv_nsec/1000000); }
966
967static jobject xltstat(JNIEnv *jni, const struct stat *st)
968{
969 jclass cls;
970 jmethodID init;
971 jint modehack;
c8292b34 972
8eabb4ff
MW
973 modehack = st->st_mode&07777;
974 if (S_ISFIFO(st->st_mode)) modehack |= 0010000;
975 else if (S_ISCHR(st->st_mode)) modehack |= 0020000;
976 else if (S_ISDIR(st->st_mode)) modehack |= 0040000;
977 else if (S_ISBLK(st->st_mode)) modehack |= 0060000;
978 else if (S_ISREG(st->st_mode)) modehack |= 0100000;
979 else if (S_ISLNK(st->st_mode)) modehack |= 0120000;
980 else if (S_ISSOCK(st->st_mode)) modehack |= 0140000;
981
982 cls = (*jni)->FindClass(jni, STAT); assert(cls);
983 init = (*jni)->GetMethodID(jni, cls, "<init>", "(IIJIIIIIIJIJJJJ)V");
984 assert(init);
985 return ((*jni)->NewObject(jni, cls, init,
986 (jint)major(st->st_dev), (jint)minor(st->st_dev),
987 (jlong)st->st_ino,
988 modehack,
989 (jint)st->st_nlink,
990 (jint)st->st_uid, (jint)st->st_gid,
991 (jint)major(st->st_rdev), (jint)minor(st->st_rdev),
992 (jlong)st->st_size,
993 (jint)st->st_blksize, (jlong)st->st_blocks,
994 xlttimespec(&st->st_atim),
995 xlttimespec(&st->st_mtim),
996 xlttimespec(&st->st_ctim)));
997}
998
999JNIEXPORT jobject JNIFUNC(stat)(JNIEnv *jni, jobject cls, jobject path)
3a2f1a4b 1000{
8eabb4ff
MW
1001 jobject r = 0;
1002 const char *pathstr = 0;
1003 struct stat st;
1004
1005 pathstr = get_cstring(jni, path); if (!pathstr) goto end;
1006 if (stat(pathstr, &st)) {
1007 except_syserror(jni, SYSERR, errno,
1008 "failed to read information about `%s'", pathstr);
1009 goto end;
1010 }
1011 r = xltstat(jni, &st);
1012end:
1013 put_cstring(jni, path, pathstr);
1014 return (r);
3a2f1a4b
MW
1015}
1016
8eabb4ff
MW
1017JNIEXPORT jobject JNIFUNC(lstat)(JNIEnv *jni, jobject cls, jobject path)
1018{
1019 jobject r = 0;
1020 const char *pathstr = 0;
1021 struct stat st;
3a2f1a4b 1022
8eabb4ff
MW
1023 pathstr = get_cstring(jni, path); if (!pathstr) goto end;
1024 if (lstat(pathstr, &st)) {
1025 except_syserror(jni, SYSERR, errno,
1026 "failed to read information about `%s'", pathstr);
1027 goto end;
1028 }
1029 r = xltstat(jni, &st);
1030end:
1031 put_cstring(jni, path, pathstr);
1032 return (r);
1033}
1034
1035struct dir {
1036 struct native_base _base;
1037 DIR *d;
3a2f1a4b 1038};
8eabb4ff
MW
1039static const struct native_type dir_type =
1040 { "dir", sizeof(struct dir), 0x0f5ca477 };
3a2f1a4b 1041
8eabb4ff 1042JNIEXPORT jobject JNIFUNC(opendir)(JNIEnv *jni, jobject cls, jobject path)
3a2f1a4b 1043{
8eabb4ff
MW
1044 const char *pathstr = 0;
1045 struct dir dir;
1046 wrapper r = 0;
3a2f1a4b 1047
8eabb4ff
MW
1048 pathstr = get_cstring(jni, path); if (!pathstr) goto end;
1049 INIT_NATIVE(dir, &dir);
1050 dir.d = opendir(pathstr);
1051 if (!dir.d) {
1052 except_syserror(jni, SYSERR, errno,
1053 "failed to open directory `%s'", pathstr);
1054 goto end;
1055 }
1056 r = wrap(jni, &dir_type, &dir);
1057end:
1058 put_cstring(jni, path, pathstr);
1059 return (r);
3a2f1a4b
MW
1060}
1061
8eabb4ff
MW
1062JNIEXPORT jbyteArray JNIFUNC(readdir)(JNIEnv *jni, jobject cls,
1063 jobject path, jobject wdir)
3a2f1a4b 1064{
8eabb4ff
MW
1065 const char *pathstr = 0;
1066 struct dir dir;
1067 struct dirent *d;
1068 jbyteArray r = 0;
3a2f1a4b 1069
8eabb4ff
MW
1070 if (unwrap(jni, &dir, &dir_type, wdir)) goto end;
1071 if (!dir.d) { except(jni, ARGERR, "directory has been closed"); goto end; }
1072 errno = 0; d = readdir(dir.d);
1073 if (errno) {
1074 pathstr = get_cstring(jni, path); if (!pathstr) goto end;
1075 except_syserror(jni, SYSERR, errno,
1076 "failed to read directory `%s'", pathstr);
1077 goto end;
1078 }
1079 if (d) r = wrap_cstring(jni, d->d_name);
1080end:
1081 put_cstring(jni, path, pathstr);
1082 return (r);
1083}
1084
1085JNIEXPORT void JNIFUNC(closedir)(JNIEnv *jni, jobject cls,
1086 jobject path, jobject wdir)
1087{
1088 const char *pathstr = 0;
1089 struct dir dir;
1090
1091 if (unwrap(jni, &dir, &dir_type, wdir)) goto end;
1092 if (!dir.d) goto end;
1093 if (closedir(dir.d)) {
1094 pathstr = get_cstring(jni, path); if (!pathstr) goto end;
1095 except_syserror(jni, SYSERR, errno,
1096 "failed to close directory `%s'", pathstr);
1097 goto end;
1098 }
1099 dir.d = 0;
1100 if (update_wrapper(jni, &dir_type, wdir, &dir)) goto end;
1101end:
1102 put_cstring(jni, path, pathstr);
3a2f1a4b
MW
1103}
1104
8eabb4ff
MW
1105/*----- A server connection, using a Unix-domain socket -------------------*/
1106
3a2f1a4b 1107struct conn {
8eabb4ff 1108 struct native_base _base;
3a2f1a4b
MW
1109 int fd;
1110 unsigned f;
1111#define CF_CLOSERD 1u
1112#define CF_CLOSEWR 2u
1113#define CF_CLOSEMASK (CF_CLOSERD | CF_CLOSEWR)
1114};
8eabb4ff
MW
1115static const struct native_type conn_type =
1116 { "conn", sizeof(struct conn), 0xed030167 };
3a2f1a4b 1117
8eabb4ff
MW
1118JNIEXPORT wrapper JNICALL JNIFUNC(connect)(JNIEnv *jni, jobject cls,
1119 jobject path)
3a2f1a4b 1120{
8eabb4ff 1121 struct conn conn;
3a2f1a4b 1122 struct sockaddr_un sun;
8eabb4ff
MW
1123 const char *pathstr = 0;
1124 jobject ret = 0;
3a2f1a4b
MW
1125 int fd = -1;
1126
8eabb4ff
MW
1127 pathstr = get_cstring(jni, path); if (!pathstr) goto end;
1128 if (strlen(pathstr) >= sizeof(sun.sun_path)) {
1129 except(jni, ARGERR,
1130 "Unix-domain socket path `%s' too long", pathstr);
1131 goto end;
1132 }
3a2f1a4b 1133
8eabb4ff
MW
1134 INIT_NATIVE(conn, &conn);
1135 fd = socket(SOCK_STREAM, PF_UNIX, 0); if (fd < 0) goto err;
3a2f1a4b
MW
1136
1137 sun.sun_family = AF_UNIX;
8eabb4ff
MW
1138 strcpy(sun.sun_path, (char *)pathstr);
1139 if (connect(fd, (struct sockaddr *)&sun, sizeof(sun))) goto err;
3a2f1a4b 1140
8eabb4ff
MW
1141 conn.fd = fd; fd = -1;
1142 conn.f = 0;
1143 ret = wrap(jni, &conn_type, &conn);
1144 goto end;
3a2f1a4b 1145
3a2f1a4b 1146err:
8eabb4ff
MW
1147 except_syserror(jni, SYSERR, errno,
1148 "failed to connect to Unix-domain socket `%s'", pathstr);
1149end:
1150 if (fd == -1) close(fd);
1151 put_cstring(jni, path, pathstr);
1152 return (ret);
3a2f1a4b
MW
1153}
1154
8eabb4ff
MW
1155static int check_buffer_bounds(JNIEnv *jni, const char *what,
1156 jbyteArray buf, jint start, jint len)
3a2f1a4b 1157{
3a2f1a4b 1158 jsize bufsz;
8eabb4ff 1159 jclass cls;
3a2f1a4b 1160
8eabb4ff
MW
1161 cls = (*jni)->FindClass(jni, "[B"); assert(cls);
1162 if (!(*jni)->IsInstanceOf(jni, buf, cls)) {
1163 except(jni, ARGERR,
1164 "expected a byte array");
1165 return (-1);
1166 }
3a2f1a4b 1167 bufsz = (*jni)->GetArrayLength(jni, buf);
8eabb4ff
MW
1168 if (start > bufsz) {
1169 except(jni, BOUNDSERR,
1170 "bad %s buffer bounds: start %d > buffer size %d", start, bufsz);
1171 return (-1);
1172 }
1173 if (len > bufsz - start) {
1174 except(jni, BOUNDSERR,
1175 "bad %s buffer bounds: length %d > remaining buffer size %d",
1176 len, bufsz - start);
1177 return (-1);
3a2f1a4b 1178 }
8eabb4ff
MW
1179 return (0);
1180}
1181
1182JNIEXPORT void JNICALL JNIFUNC(send)(JNIEnv *jni, jobject cls,
1183 wrapper wconn, jbyteArray buf,
1184 jint start, jint len)
1185{
1186 struct conn conn;
1187 ssize_t n;
1188 jbyte *p = 0;
1189
1190 if (unwrap(jni, &conn, &conn_type, wconn)) goto end;
1191 if (check_buffer_bounds(jni, "send", buf, start, len)) goto end;
3a2f1a4b 1192
8eabb4ff 1193 p = (*jni)->GetByteArrayElements(jni, buf, 0);
3a2f1a4b
MW
1194 if (!p) goto end;
1195
1196 while (len) {
8eabb4ff 1197 n = send(conn.fd, p + start, len, 0);
3a2f1a4b 1198 if (n < 0) {
8eabb4ff
MW
1199 except_syserror(jni, SYSERR,
1200 errno, "failed to send on connection");
3a2f1a4b
MW
1201 goto end;
1202 }
1203 start += n; len -= n;
1204 }
1205
1206end:
1207 if (p) (*jni)->ReleaseByteArrayElements(jni, buf, p, JNI_ABORT);
3a2f1a4b
MW
1208 return;
1209}
1210
8eabb4ff
MW
1211JNIEXPORT jint JNICALL JNIFUNC(recv)(JNIEnv *jni, jobject cls,
1212 wrapper wconn, jbyteArray buf,
1213 jint start, jint len)
3a2f1a4b 1214{
8eabb4ff 1215 struct conn conn;
3a2f1a4b
MW
1216 jbyte *p = 0;
1217 jint rc = -1;
1218
8eabb4ff
MW
1219 if (unwrap(jni, &conn, &conn_type, wconn)) goto end;
1220 if (check_buffer_bounds(jni, "send", buf, start, len)) goto end;
3a2f1a4b 1221
8eabb4ff 1222 p = (*jni)->GetByteArrayElements(jni, buf, 0);
3a2f1a4b
MW
1223 if (!p) goto end;
1224
8eabb4ff 1225 rc = recv(conn.fd, p + start, len, 0);
3a2f1a4b 1226 if (rc < 0) {
8eabb4ff
MW
1227 except_syserror(jni, SYSERR,
1228 errno, "failed to read from connection");
3a2f1a4b
MW
1229 goto end;
1230 }
1231 if (!rc) rc = -1;
1232
1233end:
1234 if (p) (*jni)->ReleaseByteArrayElements(jni, buf, p, 0);
3a2f1a4b
MW
1235 return (rc);
1236}
1237
25c35469
MW
1238JNIEXPORT void JNICALL JNIFUNC(closeconn)(JNIEnv *jni, jobject cls,
1239 wrapper wconn, jint how)
3a2f1a4b 1240{
8eabb4ff
MW
1241 struct conn conn;
1242 int rc;
1243
1244 if (unwrap(jni, &conn, &conn_type, wconn)) goto end;
1245 if (conn.fd == -1) goto end;
1246
1247 how &= CF_CLOSEMASK&~conn.f;
1248 conn.f |= how;
1249 if ((conn.f&CF_CLOSEMASK) == CF_CLOSEMASK) {
1250 close(conn.fd);
1251 conn.fd = -1;
3a2f1a4b 1252 } else {
8eabb4ff
MW
1253 if (how&CF_CLOSERD) shutdown(conn.fd, SHUT_RD);
1254 if (how&CF_CLOSEWR) shutdown(conn.fd, SHUT_WR);
3a2f1a4b 1255 }
8eabb4ff 1256 rc = update_wrapper(jni, &conn_type, wconn, &conn); assert(!rc);
3a2f1a4b
MW
1257
1258end:
3a2f1a4b
MW
1259 return;
1260}
8eabb4ff
MW
1261
1262/*----- That's all, folks -------------------------------------------------*/