[hamradio-commits] [aprx] 01/02: Import Upstream version 2.08.svn593+dfsg
Dave Hibberd
hibby-guest at moszumanska.debian.org
Sat Jan 21 23:59:21 UTC 2017
This is an automated email from the git hooks/post-receive script.
hibby-guest pushed a commit to branch master
in repository aprx.
commit 71f02d51f95f71dc1e3e9f2348633dcfa1ef2a11
Author: Hibby <d at vehibberd.com>
Date: Sat Jan 21 23:58:46 2017 +0000
Import Upstream version 2.08.svn593+dfsg
---
ChangeLog | 2539 ++++++++
INSTALL | 60 +
LICENSE | 27 +
Makefile | 226 +
Makefile.in | 226 +
PROTOCOLS | 258 +
README | 86 +
ROADMAP | 26 +
SVNVERSION | 1 +
TIMESTAMP-AT-APRSIS | 264 +
TODO | 109 +
VER | 1 +
VERSION | 1 +
ViscousDigipeater.README | 60 +
ViscousDigipeaterTxEffect.png | Bin 0 -> 30001 bytes
agwpesocket.c | 731 +++
apparmor.aprx | 17 +
aprsis.c | 1279 ++++
aprx-complex.conf.in | 528 ++
aprx-config.xsd | 263 +
aprx-rxigate.conf.in | 118 +
aprx-stat.8.in | 217 +
aprx-stat.c | 258 +
aprx.8.in | 1428 +++++
aprx.c | 635 ++
aprx.conf.in | 390 ++
aprx.h | 784 +++
aprx.spec | 118 +
aprxpolls.c | 51 +
ax25.c | 295 +
beacon.c | 1235 ++++
build-stamp | 0
cellmalloc.c | 357 ++
cellmalloc.h | 31 +
config.c | 814 +++
config.h.in | 182 +
configure | 6245 ++++++++++++++++++++
configure-stamp | 0
configure.in | 318 +
coverity-build-submit.sh | 27 +
crc.c | 286 +
debian/aprx.default | 10 +
debian/aprx.init | 155 +
debian/aprx.postinst.debhelper | 15 +
debian/aprx.postrm.debhelper | 5 +
debian/aprx.prerm.debhelper | 9 +
debian/aprx.substvars | 1 +
debian/aprx/DEBIAN/conffiles | 5 +
debian/aprx/DEBIAN/control | 19 +
debian/aprx/DEBIAN/md5sums | 13 +
debian/aprx/DEBIAN/postinst | 17 +
debian/aprx/DEBIAN/postrm | 7 +
debian/aprx/DEBIAN/prerm | 11 +
debian/aprx/etc/apparmor.d/sbin.aprx | 17 +
debian/aprx/etc/default/aprx | 10 +
debian/aprx/etc/init.d/aprx | 155 +
debian/aprx/etc/logrotate.d/aprx | 8 +
debian/aprx/usr/sbin/aprx | Bin 0 -> 454267 bytes
debian/aprx/usr/sbin/aprx-stat | Bin 0 -> 29954 bytes
debian/aprx/usr/share/doc/aprx/LICENSE | 27 +
debian/aprx/usr/share/doc/aprx/PROTOCOLS.gz | Bin 0 -> 3521 bytes
debian/aprx/usr/share/doc/aprx/README | 86 +
debian/aprx/usr/share/doc/aprx/ROADMAP | 26 +
debian/aprx/usr/share/doc/aprx/TODO.gz | Bin 0 -> 2186 bytes
.../aprx/usr/share/doc/aprx/aprx-complex.conf.gz | Bin 0 -> 7003 bytes
debian/aprx/usr/share/doc/aprx/aprx-manual.pdf.gz | Bin 0 -> 465580 bytes
debian/aprx/usr/share/doc/aprx/aprx.conf.gz | Bin 0 -> 5375 bytes
debian/aprx/usr/share/doc/aprx/copyright | 1 +
debian/aprx/usr/share/man/man8/aprx-stat.8.gz | Bin 0 -> 2507 bytes
debian/aprx/usr/share/man/man8/aprx.8.gz | Bin 0 -> 15564 bytes
debian/changelog | 12 +
debian/compat | 1 +
debian/control | 22 +
debian/copyright | 1 +
debian/dirs | 6 +
debian/docs | 8 +
debian/files | 1 +
debian/postinst.off | 49 +
debian/rules | 96 +
digipeater.c | 1867 ++++++
doc/aprx-manual-pics.odp | Bin 0 -> 12644 bytes
doc/aprx-manual.odt | Bin 0 -> 197896 bytes
doc/aprx-manual.pdf | Bin 0 -> 438348 bytes
doc/aprx-requirement-specification.odt | Bin 0 -> 34162 bytes
doc/aprx-requirement-specification.pdf | Bin 0 -> 175248 bytes
dprsgw.c | 1094 ++++
dupecheck.c | 484 ++
erlang.c | 703 +++
filter.c | 2331 ++++++++
filter.c.2.06-to-head.diff | 585 ++
historydb.c | 631 ++
historydb.h | 101 +
hlog.c | 561 ++
hlog.h | 57 +
igate.c | 650 ++
install-sh | 507 ++
interface.c | 1895 ++++++
keyhash.c | 91 +
keyhash.h | 17 +
kiss.c | 710 +++
logrotate.aprx.in | 8 +
man-to-html.sh | 118 +
netax25.c | 810 +++
netresolver.c | 151 +
parse_aprs.c | 1416 +++++
pbuf.c | 237 +
pbuf.h | 125 +
rpm/aprx.default | 10 +
rpm/aprx.init | 84 +
rpm/aprx.service | 11 +
ssl.c | 920 +++
ssl.h | 102 +
svnversion | 3 +
svnversion-test.sh | 36 +
telemetry.c | 572 ++
test.c | 21 +
timercmp.c | 157 +
timestamp.c | 184 +
tt.5383 | 3101 ++++++++++
ttyreader.c | 1080 ++++
valgrind.c | 101 +
121 files changed, 42815 insertions(+)
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..1affd13
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,2539 @@
+2014-03-24 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * interface.c:
+ Igated 3rd-party frames are produced with different TNC2 format
+ message from AX.25 frame message. This is because TNC2 format
+ is used at filter processing, and needs to have original source
+ address arriving from APRS-IS.
+
+ * ttyreader.c:
+ Set default read timeout to 60 minutes, it can be configured to
+ any value from 1 second to 4 hours as channel busyness supports,
+ and even disabled.
+
+ * ttyreader.c:
+ Use aprxlog() to record tty state changes: close/open
+
+ * netresolver.c:
+ Use same "die_now" flag as all other thread loops do.
+
+ * historydb.c:
+ Fix key pickup of sourcename.
+ Debug print key in history_db_insert_()
+
+ * agwpesocket.c:
+ Code cleaning.
+
+ * cellmalloc.c:
+ No need to include <pthread.h>
+
+ * configure.in:
+ Make pthread autoconfig default with option to disable it.
+
+ * pbuf.c:
+ Declare pbuf_alloc() static.
+
+2014-03-22 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.c:
+ Fix aprxlog() function internal varargs usage.
+ It must be reset for each vfprintf() call...
+
+ * aprx.c:
+ Initial value of time_reset = 1; start in "reset state",
+ which does time resetting in all prepoll codes in main
+ loop. At second round and there after of the main loop
+ the reset_time flag will be reset after execution of all
+ prepoll functions.
+
+ * aprsis.c:
+ Do initial connect at about current tick + 10 seconds.
+ Reset phase will also put next connect attempt at that time.
+
+ * aprx.c:
+ Change stdout and stderr buffering definition to have
+ a buffer, and make line buffering in a way that works..
+
+ * aprsis.c:
+ Correct calls to aprxlog().
+
+ * configure.in, Makefile.in:
+ Revised the pthread support autoconfig to really find where
+ the libraries are, and set correct definitions and compile/link
+ time parameters.
+
+2014-03-21 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.h, interface.c, beacon.c, config.c, digipeater.c,
+ telemetry.c:
+ Change the "flags are bits in an int" to "packet bit fields"
+ allowing direct setting of flags.
+
+ * aprx.c, beacon.c:
+ Do not use stdout FILE* to print things out of signal handler.
+ Use sprintf(3) to put things into local buffer, then use write(2)
+ to send them to file handle 1. Otherwise GLIBC has some
+ interlock issue causing a deadlock in rare case. (Happened
+ with debug printouts in development.)
+
+ * beacon.c:
+ Correct close() of exec type beacon file descriptor.
+
+ * interface.c:
+ Correct primary <interface> flags defaults, and setting of
+ "telem-to-is <boolean>" and "telem-t-rf <boolean>" flags.
+
+ * telemetry.c:
+ Debug-printout (debug>1) of telemetry about time until next
+ output, plus packet that is being telemetered along with
+ hex coding of interface flags. Also streamlined the telemetry
+ label output code.
+
+2014-03-11 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * ttyreader.c:
+ Do open /dev/ttySnnn up front in non-blocking mode so that
+ if the serial port hardware needs to be wired specially to
+ be considered active ( = flow control ) the serial port open
+ will not stop -- hang -- at it missing.
+
+ * agwpesocket.c:
+ Update time comparison codes to current model.
+
+ * aprx.c:
+ time_reset gets cleared only after first round through
+ the main loop.
+
+ * beacon.c:
+ The 'file' and 'exec' directives need at most 256 bytes of read
+ buffer to get beacon body content. No need for 2kB buffer alloc.
+
+2014-03-08 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * digipeater.c:
+ Corrected the digipeating recognition algorithm.
+ If the current leading VIA field value without
+ H-bit set is not recognized as transmitter callsign,
+ transmitter alias, or value of <trace> or <wide>
+ directive at digipeater source or digipeater transmitter
+ configuration, then the packet is _not_ eligible for
+ digipeat.
+
+2014-03-08 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.h, beacon.c, digipeater.c, parse_aprs.c, telemetry.c,
+ timercmp.c, ttyreader.c:
+ Fix all things flagged at CFLAGS="-g -O2 -Wall".
+
+ * configure.in, config.h.in, aprx.c:
+ Autoconf test for <sys/wait.h>, and include if available.
+
+ * aprx.h, beacon.c, config.c, digipeater.c, interface.c, netax25.c,
+ telemetry.c, doc/aprx-manual.odt, aprx.8.in:
+ Change single boolean flag 'txok' into multiple interface flags
+ where the 'txok' is just one bit. Added 'telem-to-is <boolean>'
+ option to <interface>
+
+ * aprsis.c:
+ Warn at startup if an <aprsis> block does not contain passcode.
+
+2014-03-04 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.c, aprx.h, beacon.c:
+ Pickup child process exit SIGCHLD signals, and track
+ which child did exit.
+
+ * beacon.c, aprx.c, aprx.h, aprx.conf.in, aprx.8.in:
+ Kamil Palkowiski SQ8KFH's beacon exec idea rewritten to be
+ non-blocking in execution. The Aprx main loop does not
+ tolerate blocking programs.
+
+ * aprsis.c, ttyreader.c:
+ poll(2) results on file handles asking for POLLIN can include
+ POLLHUP and POLLERR.
+
+2014-03-04 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.8.in, doc/aprx-manual.odt:
+ Document <aprsis> passcode a bit more.
+
+ * aprsis.c, aprx.c, aprx.h, netax25.c:
+ Move aprxlog() from aprsis.c to aprx.c.
+ Use aprxlog() instead of open coded logger at netax25.c.
+ Modify which type of APRSIS events get logged without
+ the runtime -L option to the aprxlogfile.
+
+2014-02-26 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * Makefile.in, agwpesocket.c, aprsis.c, aprx-stat.c, aprx.c, aprx.h,
+ aprxpolls.c, beacon.c, config.h.in, configure.in, digipeater.c,
+ dprsgw.c, dupecheck.c, erlang.c, filter.c, historydb.c, hlog.c,
+ igate.c, interface.c, kiss.c, netax25.c, netresolver.c, pbuf.c,
+ telemetry.c, timercmp.c, ttyreader.c:
+ Change primary time management to use system monotonic clock
+ instead of time-of-day clock that can jump back/forward.
+ This clock is in -lrt as: clock_gettime(CLOCK_MONOTONIC, ...)
+ and it is the kernel internal primary jiffy lock.
+ This value is not synchronized with wall clock, although it
+ does proceeds at about same rate. It is also present at FreeBSD,
+ and presumably several other platforms too. (It is a POSIX thing,
+ after all.)
+
+ The system also watches over if the delta in between subsequent
+ value extracts exceeds about 30 seconds, or the time jumps
+ backwards at all. If such happens, next call cycle on all prepoll
+ routines are resetting all time management things in particular
+ subsystem.
+
+ Renamed the primary time tracking variable from "now" to "tick",
+ as it has no correlation with current wall-clock time.
+
+ There are no Y2038 issues in this code approach.
+
+
+2014-02-24 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * agwpesocket.c, aprsis.c, digipeater.c, dptsgw.c, dupecheck.c,
+ filter.c, interface.c, netresolver.c:
+
+ Y2038 comparison things of time values. Somewhat premature, but
+ gets things in line with uses of 'struct timeval' -- tv_timercmp().
+
+2014-02-23 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * all files:
+ Change copyright statement to read 2007-2014.
+
+ * timercmp.c, Makefile.in:
+ New file bringing all struct timeval tools to one place.
+ Added tv_timerbounds() that monitors time targets vs.
+ current time value -- if machine has hibernated a lot
+ without running timers and the 'now' jumps ahead a lot
+ (or backwards for complete coverage), then the target
+ time is reset. This avoids running a burst of beacon
+ events when 'now' jumps ahead a lot, as an example.
+
+ * netax25.c, digipeater.c, beacon.c, ttyreader.c, telemetry.c, dprsgw.c,
+ agwpesocket.c, aprsis.c, aprx.h, historydb.c, dupecheck.c, erlang.c:
+ Timer tracking against possibly jumping 'now' with conditional
+ resetting.
+
+ * beacon.c, dupecheck.c, netax25.c, telemetry.c, timercmp.c:
+ Few wrong initializations of timers fixed.
+
+2014-02-13 Forgot to record the author
+
+ * debian/control:
+ Add build and run dependency of OpenSSL.
+ (Actually not yet necessary, SSL support is not being
+ compilable / compiled yet.)
+
+2014-02-13 Heikki Hannikainen <hes at aprs.fi>
+
+ * parse_aprs.c:
+ Check that the timestamp ends with one of valid timestamp type ids.
+ Those being: 'z', 'h', '/'
+
+2014-02-03 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * ttyreader.c:
+ Read only when poll reports data availability. (uh3ack)
+
+2014-02-01 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * rpm/aprx.spec.in:
+ Forcing RPM building to produce i386 package.
+ (There is no need to produce x86-64 binary package.
+ Small footprint is the goal.)
+
+ * erlang.c:
+ Do not call aprx_syslog_init() from this module.
+
+ * ssl.c, ssl.h, hlog.c, hlog.h:
+ Copied SSL client code material from Aprsc. Not yet functional.
+
+ * agwpesocket.c, beacon.c, digipeater.c, dupecheck.c, erlang.c,
+ telemetry.c, ttyreader.c:
+ If the time reported by time(2) seems to jump ahead or backwards
+ too much, various deadline schedulers keeping time goals in mind
+ will reset themselves upon detection of the condition.
+
+2013-11-01 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * historydb.c:
+ Initialize also tokenbucket value so that freshly arrived
+ packet at the historydb will have at least 1.0 tokens
+ at hand to permit one initial digipeat. Will not be enough
+ for multi-transmitter case.
+
+ * digipeater.c:
+ Display Ratelimit caused packet drops at all debug levels.
+ ( Instead of >1 )
+
+ * filter.c:
+ Correct parse of s// filter. (Original author not recorded.)
+
+2013-10-08 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * config.c, digipeater.c:
+ Add conditionals so that --disable-igate works.
+
+2013-10-07 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * Makefile.in, svnversion-test.sh:
+ A bit more complicated way to pick up SVNVERSION information.
+
+ * aprx.c, VERSION, README, TODO, INSTALL, doc/aprx-manual.odt
+ Version 2.08 under way.
+
+ * aprx.h, aprx.c, aprsis.c, aprx-stat.c, aprxpolls.c, beacon.c,
+ digipeater.c, dupecheck.c, erlang.c, interface.c, telemetry.c,
+ ttyreader.c:
+ Changed time tracking from "time_t" to "struct timeval".
+ This supports (sub)millisecond granularity e.g. in KISS
+ polling, and overall smoothness of processing.
+ No, it isn't really necessary besides of that KISS polling,
+ but unified API is always better.
+
+2013-10-06 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * filter.c:
+ Add 'g/call1/call2..' filter to match "group messaging" recently
+ added on javAPRSSrvr.
+
+ * doc/aprx-manual.odt:
+ Document internally supported APRS-IS style filter tokens.
+
+ * filter.c:
+ Code cleaning around reference definitions.
+
+ * filter.c:
+ * Add feature to recognize 3rd-party frames by type (t/3)
+ * Enable support of D filter (they are meaningful at RF too)
+
+ * filter.c, aprx.c, aprx.h, config.c:
+ * Fix M filter preparation to include cos(lat).
+
+ * aprx.h, interface.c, parse_aprs.c:
+ Always look inside 3rd party APRS frames too to determine
+ their content for filtering uses. RF->RF relaying didn't
+ do that.
+
+2013-10-05 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * configure.in, Makefile.in, aprx.c, aprx.8.in:
+ Support integrated binary version printout:
+ aprx -V
+
+ * configure.in, Makefile.in:
+ Preparing for new code features, picking up more
+ library locations.
+
+ * aprsis.c:
+ Improve the error diagnostics on upstream socket
+ connection error situations.
+
+ * beacon.c:
+ Debug report number of parsed beacons.
+
+2013-10-03 Geoffrey F4FXL <max at planetemax.com>
+
+ * digipeater.c:
+ Fixed badly formatted AX.25 after insertion of MYCALL
+ value on VIA path. The end-pointer was mis-managed.
+
+2013-08-10 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * configure.in:
+ Look for OpenSSL library, SCTP network protocol.
+
+2013-08-03 Heikki Hannikainen <oh7lzb at sral.fi>
+
+ * aprsis.c, aprs-complex.conf.in, aprx.8.in, aprx.conf.in,
+ doc/aprx-manual.odt:
+ Require manually configured APRS-IS passcode.
+
+2013-08-03 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * agwpesocket.c, aprsis.c, beacon.c, cellmalloc.c, dprsgw.c,
+ dupecheck.c, filter.c, historydb.c, keyhash.c, pbuf.c,
+ telemetry.c, ttyreader.c:
+ Coding style change: Use cmalloc() instead of malloc() + memset().
+
+2013-08-03 Frank Knobbe <frank at knobbe.us>
+
+ * interface.c:
+ Correct the way how messages are passed from APRS-IS to RF.
+ There was a parse bug around pb->dstname field.
+
+2013-04-25 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * netax25.c:
+ Receive from AX.25 interface only if it has associated
+ an Aprx interface configuration object with it.
+ Less noise on logs that way.
+
+2013-04-25 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * filter.c:
+ Commented out code, but make it conform with API
+ of historydb.
+
+ * parse_aprs.c:
+ The call parameter 'historydb' can be NULL,
+ don't SEGV on it.
+
+ * telemetry.c:
+ Pointer writing to beyond end of buffer (one last time)
+
+ * ttyreader.c:
+ hexdumpfp() text dump fix
+
+ * beacon.c:
+ Restructured the beacon data preparation for transmit.
+ Lots of mistakes had crept in recently (2.06?)
+
+ * interface.c, netax25.c:
+ Debug printouts of processing errors, and transmissions.
+
+2013-04-23 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * config.c:
+ Show 'myloc' lat/lon in degrees instead of radians.
+
+ * digipeater.c:
+ debugging..
+
+ * filter.c:
+ A fence-post error while parsing o/A8CDEF-12 -- accepted
+ only A8CDEF-1.
+
+2013-04-22 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.8.in, aprx-complex.conf.in, aprx.conf.in,
+ doc/aprx-manual.odt, doc/aprx-manual.pdf:
+ Change documentation wording around <beacon>.
+
+ * config.c:
+ Fix myloc config parameter parsing.
+
+ * beacon.c:
+ Every <beacon> group is now independent from each other.
+ If you define multiple <beacon> groups, they will be
+ running in parallel with their own scheduling.
+ Meaning that you can have different cycles, and defaults
+ at each.
+
+ * all source files:
+ Change Copyright claim years to 2007-2013.
+
+ * configure.in:
+ Auto-test for <netinet/sctp.h> header file.
+
+2013-04-22 Geoffrey F4FXL <max at planetemax.com>
+
+ * digipeater.c:
+ Recent rewrite of viafield tracking code forgot
+ to initialize it in many cases -- and Covarity
+ tool did not notice that :-(
+
+2013-04-20 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprsis.c:
+ Preparing for new communication modes
+
+ * parse_aprs.c:
+ Two condition expression goofups that were always
+ "false"
+
+ * filter.c:
+ Match filter parsing with aprsc (issue found with
+ Covarity testing)
+
+ * aprsis.c, aprx.c, aprx.h, aprxpolls.c, cellmalloc.c,
+ config.c, digipeater.c, dprsgw.c, filter.c, interface.c,
+ kiss.c, netax25.c, telemetry.c, ttyreader.c:
+ Covarity testing revealed unclear error cases, unnecessary
+ comparisons against NULL, and other mostly harmless things.
+ There is still a memory leak in interface.c configuration
+ parsing in error path -- but that is not a big deal.
+
+ * digipeater.c:
+ Modified "fixall" processing to put transmitter
+ callsign into first VIA field in every case,
+ possibly truncating the VIA list in order to make
+ room for the callsign in the incoming request.
+ Replacing numeric literals with #define constants
+ to inline document of what is going on at places.
+
+2013-04-18 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.c, VERSION:
+ Version 2.07 (APRX27)
+
+ * aprx.8.in:
+ Document "myloc" and "$myloc", "filter m/100", etc.
+
+ * aprx.c, aprx.h:
+ Variables for myloc_lat, myloc_lon, and string forms.
+
+ * config.c:
+ Parse "myloc lat xx long yy" entry.
+ Moved couple tool functions here from beacon.c.
+
+ * filter.c:
+ Support "m/100", if top-level configuration has
+ "myloc lat xx lon yy" entry.
+
+ * beacon.c:
+ new "macro" definition of "$myloc", which takes
+ lat+lon of top-level configuration "myloc.." entry.
+
+ * interface.c:
+ Recognizing incoming messages targeted to this server
+ (by $mycall, and transmit interface callsigns.)
+ Processing them by rudimentary acknowledgement, and
+ optional place to actually process them.
+ Actual processing is still not written.
+
+2013-02-02 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * ttyreader.c:
+ Improve the error report, when serial port opening failed.
+
+ * filter.c:
+ Coverity reports over aprsc reflect directly to aprx too.
+ We use same code in parts...
+
+2012-12-29 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * digipeater.c, aprx.h, historydb.h:
+ Track digipeated messages by source callsign.
+
+ * aprsis.c:
+ Use A->rdlin_len instead of strlen(A->rdline)
+
+ * aprx-stat.c, aprx.c, aprx.h, erlang.c:
+ Moved syslog init from erlang to aprx core.
+
+ * parse_aprs.c, aprx.h:
+ Function parse_aprs_message() for planned future use.
+
+2012-12-09 Matt Maguire VK2RQ <matt.vk2rq at gmail.com>
+
+ * telemetry.c:
+ Sometimes the Erlang registery contains interface
+ entries that are no longer actually in the system.
+ Avoid NULL referral at such situations.
+
+2012-11-09 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * dprsgw.c:
+ Better accounting on DPRS side-channel data occupancy,
+ assuming the DPRS gw does send bytes all the time.
+
+2012-11-09 FUJIURA Toyonori JG2RZF <toyokun at gmail.com>
+
+ * dprsgw.c:
+ Add receiver Erlang estimators to received packets.
+
+2012-11-01 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprsis.c:
+ Move CRLF appending later into APRSIS transmission.
+
+2012-10-31 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.c:
+ Use thread-safe gmtime_r() instead of gmtime() for time printout.
+
+2012-10-29 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * Makefile.in:
+ Improve distribution tar RPM building support.
+
+ * rpm/aprx.init, rpm/aprx.service, rpm/aprx.spec.in:
+ RPM package packing files
+
+ * filter.c:
+ Clean filter parser.
+
+ * aprx.c:
+ Acquire a lock on pid-file, and keep the file open.
+
+2012-10-29 FUJIURA Toyonori JG2RZF <toyokun at gmail.com>
+
+ * dprsgw.c:
+ D-STAR callsign formatting for suffixless ones:
+ * "JG2RZF A" -> "JG2RZF-A"
+ * "JG2RZF " -> "JG2RZF-" Oops!
+
+2012-10-12 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * rpm/aprx.spec.in, rpm/aprx.service:
+ Andrew Elwell <Andrew.Elwell at gmail.com> supplied better
+ version of Fedora, and rhel.
+
+ * filter.c:
+ Leave the "over-long parameters are rejected" logic, but don't
+ support alternate group field splitter input.
+
+2012-10-03 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * TODO, INSTALL, aprx.c, aprx-manual.odt, aprx-manual.pdf,
+ VERSION: 2.06
+
+2012-10-02 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * filter.c:
+ Parse filters with callsign sets without overflowing buffer.
+ Officially syntax is: OP/callsign1/callsign2 but support
+ also: OP/callsign1,callsign2
+
+ * digipeater.c:
+ Correct analysis of first "via" field, bail out of the loop
+ only after it has been parsed.
+
+2012-10-01 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * debian/postinst, debian/aprx.init:
+ Removed postinst from debian installations. That script is
+ bad and causes install/upgrade to stall. Modernized the
+ aprx init-script.
+
+2012-09-30 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.h, digipeater.c, interface.c, pbuf.h, pbuf.c, parse_aprs.c:
+ Sync pbuf_new() implementation with aprsc's approach, and
+ sync parse_aprs() with aprsc's code. This should fix some
+ strange MICe bugs, among others.
+
+2012-09-18 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * parse_aprs.c:
+ Corrected MICe longitude degrees parser. Again.
+ (Sorry, F4FXL's parser fix was wrong.)
+
+2012-09-05 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * apparmor.aprx, debian/rules, debian/dirs, debian/postint:
+ Copied bits of Aprsc's debian packaging to Aprx.
+ (Original packaging stuff was copied from Aprx to Aprsc.)
+
+2012-09-02 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.8.in, aprx.conf.in, aprx-complex.conf.in, aprx-rxigate.conf.in,
+ configure.in:
+ Exterminated all instances of yours truly's own address from
+ the sample configurations, and replaced them with 0000.00N
+ 00000.00E coordinate, which aprs.fi treats as invalid.
+
+ * beacon.c:
+ In addition to the long time used qTYPE_LOCALGEN, added on
+ APRSIS beacons also ",TCPIP*" at the tail of whatever is
+ being sent. Pete is not happy with q-code alone...
+
+2012-08-26 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * digipeater.c, interface.c, aprx.h, TODO, aprx-manual.*:
+ Add to APRSIS tx-gated packets optionally different via-path
+ (parameter: msg-path) for message packets, than for any other
+ type packets. For example: via-path WIDE1-1, msg-path WIDE2-2
+ (By request of OH3BK.)
+
+ * VERSION: 2.05
+
+ * beacon.c, interface.c:
+ Support beaconin to APRSIS without having any radio interfaces.
+ Make null-device interface and (in special conditions the internal
+ APRSIS interface) beaconable.
+
+ * doc/aprx-manual.*:
+ Fixes on <beacon> explanations.
+ Diagrams on subsystem configuration examples describing message
+ flows.
+
+2012-08-07 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * configure.in, aprsis.c, ROADMAP, doc/aprx-manual.odt:
+ Fix the "pthreads" typo by using correct "pthread" spelling
+ in the documents + "--with-pthreads" option alias in configure.
+
+2012-08-07 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * interface.c:
+ Automatically create igate-group N values for radio interfaces
+ start from 1, and go onwards. Manually defined values start from
+ one, and it is up to the configuration writers to have a sane
+ upper limit.
+
+ * aprx.c, aprx.h, ttyreader.c, erlang.c, kiss.c:
+ Fixed a) poll of KISS subinterfaces, b) KISS polling time
+ management issues. The polling does not yet have serial-device
+ specific timing management, rather a global cadence.
+
+ * aprx.c:
+ Version 2.05
+
+ * ttyreader.c, aprxpolls.c, aprx.c, aprx.h:
+ Do millisecond timings, send KISS POLL (0x0E) code requests every
+ configured number of milliseconds per serial-device:
+ serial-device ... KISS pollmillis 100
+
+ * agwpesocket.c, aprsis.c, aprx.c, aprx.h, aprxpolls.c, aprx-stat.c,
+ beacon.c, configure.in, digipeater.c, dprsgw.c, dupecheck.c,
+ erlang.c, filter.c, historydb.c, igate.c, interface.c, kiss.c,
+ netax25.c, netresolver.c, pbuf.c, telemetry.c, ttyreader.c:
+ Changed "now" from type "time_t" to "struct timeval" enabling
+ the ttyreader to do millisecond level timing operations.
+
+2012-08-06 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * debian/aprx.init, rpm/aprx.init:
+ Correct the package start dependency definitions.
+
+2012-08-04 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * interface.c, kiss.c:
+ Debug printout claimed reversed logical meaning in history-db
+ lookup. Typo fixes at kiss processing comments.
+
+ * interface.c:
+ Config parser bug in defining multiple <kiss-subif ..>.
+ Thanks to N2PYI for report.
+
+2012-07-31 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * configure.in, digipeater.c, aprx.h, valgrind.c:
+ Supply implementation of memrchr() if the platform does not have
+ it. (GNU Libc extension, not a standard POSIX thing.)
+
+ * Makefile.in:
+ Fixing details around how to run "make make-deb".
+ Fixing how SVNVERSION is determined.
+
+ * interface.c, igate.c, aprx.h:
+ rflog() parameter set modification, show directly at calls if
+ the logged item is 'R' or 'T'.
+
+ * dupecheck.c, aprsis.c, igate.c, aprx.h, digipeater.c:
+ Prepare for the duplicate checker to be able to control the time
+ window of the duplicate checks - minimum will be 30 seconds, maximum
+ can be higher.
+
+ * digipeater.c:
+ TNC2 format quirks in "VIA field" data caused missed identification
+ of "ping pong relay" - where a packet contains this node's
+ transmitter callsign, and this node has logged its address as
+ "sent through me". Also known as TRACE mode:
+ SRC>DEST,MYTRANS,OTHERTX*,WIDE3-1
+
+2012-07-30 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * keyhash.c, keyhash.h, netax25.c, netresolver.c, interface.c,
+ cellmalloc.c, config.c, dprsgw.c, ax25.c:
+ A compiler test with -Wall complained a bit around missing ANSI-C
+ prototypes.
+
+2012-07-29 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * keyhash.c, filter.c:
+ Threw away the long unused CRC32 stuff, and added a hashkeyuc()
+ function. Fixed the way how filter_entrycall_*() and
+ filter_wx_*() functions processed the key. Now both of
+ them store keys as converted to upper case, and lookup
+ with mixed case.
+
+ * aprx-config.xsd:
+ An attempt at writing a "formal" configuration file syntax
+ description. It is really not XML that config file, nor
+ can XSD define it clearly, I think..
+
+2012-07-28 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * VERSION: 2.04
+
+ * filter.c:
+ Wrong way to put len* fields into an union vs. a few other things.
+
+ * dupecheck.c, digipeater.c:
+ Fix from iw3ijq + k3pdk about AX25 duplicate tracking dropping null
+ pointers. This has _not_ been validated for TNC mode frames yet,
+ but now AX.25 works properly.
+
+2012-01-18 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * VERSION: 2.03test4
+
+ * interface.c:
+ Record outbound "history heard" per interface.
+
+ * historydb.c:
+ Simple refactoring to use same hash function all the time.
+
+ * digipeater.c:
+ Feed all transmitted packets to dupe-filter of that transmitter.
+ This will handle cross-band digipeat + tx-gates so that
+ an APRS packet transmitted to a channel won't be digipeated
+ again by this transmitter.
+
+ * igate.c:
+ When constructing APRSIS originated 3rd-party tx-igate
+ packets, use correct version of "TCPIP" in the header.
+
+2012-01-07 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * VERSION: 2.03test3
+
+ * pbuf.h, pbuf.c, aprx.h:
+ New infrastructure function: pbuf_fill() that does
+ former two (then 3) copies of same code in interface.c
+
+ * interface.c:
+ Use new pbuf_fill().
+ On 3rd-party receiver, parse original (sort of) TNC2 frame
+ to temporary AX.25 frame for filter analysis.
+ Rearranged filter running and reactioning.
+
+ * filter.c:
+ Debug printouts
+
+ * All files...
+ Copyright text update.
+
+2011-12-30 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * interface.c:
+ 3rd-party igate tx processing fixes, now it allows also
+ other than messages
+
+2011-12-25 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * VERSION, aprx.c, TODO, INSTALL, doc/aprx-manual.*:
+ Version 2.03
+
+ * interface.c:
+ At top-level of the <interface> the "callsign" parameter
+ did not override device defined callsign and its AX.25
+ parse result.
+
+ * aprx.h, config.c, dprsgw.c, logrotate.aprx.in,
+ aprx.conf.in, aprx-complex.conf.in:
+ Configurability of logging parameter "dprslog".
+
+ * aprx.h, aprsis.c, beacon.c, igate.c, telemetry.c:
+ Supply qcode on outgoing packets depending it being
+ locally generated (qAS) or rx-i-gated (qAR)
+
+ * telemetry.c:
+ Fix telemetry "tocall" to use software identifier,
+ and to put "TCPIP*" on APRSIS beacon path.
+
+ * aprx.conf.in:
+ Fix default server list
+
+ * crc.c, kiss.c:
+ Document polynomes and fix comments
+
+2011-08-29 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * parse_aprs.c:
+ F4FXL found bug in position parser cos() pre-calculator.
+ Input needs to be in radians, it was in degrees.
+
+2011-07-26 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * interface.c:
+ If preceding address processing detects an error, do not call
+ packet transmitter.
+
+ * aprx.h, dprsgw.c, parse_aprs.c:
+ Moved DPRS related APRS symbol translation from parse_aprs.c
+ to dprsgw.c.
+
+ * parse_aprs.c:
+ Introduced APRS symbol mapper/generator for GPS messages.
+
+ * parse_aprs.c:
+ Rewrite of parse_aprs_mice() degrees parser inspired by F4FXL.
+
+2011-03-16 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * beacon.c, telemetry.c, aprx.h, interface.c:
+ Transmit only radio data port related beacons,
+ and telemetry. Don't touch on other pseudo-interfaces.
+
+2011-01-02 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * interface.c, agwpesocket.c, aprx.h:
+ Configure <interface> agwpe-device.
+
+ * netresolv.c:
+ Oops.. fixed functionality. Tuned debugging.
+
+ * aprsis.c, aprx.c:
+ Tuned debugging.
+
+ * configure.in, aprsis.c:
+ Autoconfig stdarg.h existence, use varargs on debug printout code.
+
+ * agwpesocket.c:
+ Debug printout on received frame.
+
+ * interface.c, doc/aprx-manual.odt:
+ Unlimit the number of "igate-group N" parameter's N value range.
+
+ * aprsis.c, aprx.c, beacon.c, config.c, digipeater.c, dprsgw.c,
+ filter.c, historydb.c, igate.c, interface.c, keyhash.c, kiss.c,
+ netresolver.c, ttyreader.c:
+ Cleaning compiling on a very old FreeBSD -- essentially K&R C
+ compiler (gcc 2.95.4) not permitting variable declarations
+ anywhere but beginning of code block.
+
+2011-01-01 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * interface.c:
+ Always init interface specific Erlang accounting
+
+ * filter.c:
+ Negative ranges on filters means "outside the distance".
+ This allows adding "substractive" filters that tell
+ "don't pass messages to receipients outside given range".
+
+ * configure.in, interface.c, cellmalloc.c, timestamp.c,
+ kiss.c, historydb.h, netax25.c, pbuf.h, aprx.c, aprx.h,
+ filter.c:
+ Clean autoconfig tests, make the code compilable on oldish
+ FreeBSD.
+
+ * configure.in, aprx.h, aprx.c, agwpesocket.c, interface.c:
+ Configuration option --enable-agwpe to enable AGWPE socket
+ interface code.
+
+ * configure.in, aprx.h, aprx.c, aprsis.c, ax25.c, beacon.c,
+ digipeater.c, dpwsgw.c, erlang.c, filter.c, historydb.c,
+ igate.c, interface.c, parse_aprs.c, telemetry.c, ttyreader.c:
+ Configuration option --disable-igate for embedding
+ code without igate network connection. Disables DPRS GW code,
+ and all of historydb, igate.c, aprsis.c.
+
+ * aprx.c, VERSION, INSTALL, README, TODO
+ Version: 2.02
+
+ * aprx.h, interface.c:
+ A null-device for sinking infinite digipeat output.
+ A debug tool.
+
+ * aprx.h, interface.c, historydb.c, historydb.h, pbuf.h:
+ Properly track "heard from interface(group)" information
+ for tx-igate processing. Now a full APRSIS traffic feed
+ won't transmit anything to RF, unless explicit manual
+ filter definitions tells to transmit, or message recipient
+ has been recently heard behind a radio channel X.
+ See the new 'igate-group' parameter on documents.
+
+ * aprsis.c, netresolver.c:
+ Cleaned pthreads thread creation calls.
+
+ * agwpesocket.c:
+ Erlang account on outbound socket.
+
+ * digipeater.c:
+ Allow ratelimit parameter to be defined very high.
+ This way a full APRSIS traffic feed can be sent to null-device.
+
+ * aprx.conf.in, aprx-complex.conf.in:
+ Some new notes on digipeater and tx-igate controls.
+
+ * doc/aprx-manual.odt:
+ Document update.
+
+2010-12-18 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * interface.c, aprx.h:
+ Add a "null-device" for system. Never receive anything from it,
+ have infinite transmit capacity.
+
+ * netresolver.c, aprx.h, aprx.c:
+ A pthread that resolves dynamic DNS names on background,
+ and updates static copy of address data so that main loop
+ can safely do asynchronous connections without needing
+ to do synchronous DNS resolving processing.
+
+ * aprsis.c:
+ Use pthread_cancel() in smart way to expedite shutdown.
+
+ * agwpesocket.c, aprx.h, aprx.c:
+ Draft of a socket communication mechanism with AGWPE, and LDSPED.
+
+2010-12-17 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * VERSION, INSTALL, README, TODO
+ Version: 2.01
+
+ * doc/aprx-manual.odt:
+ Write a few pages about debugging
+
+ * aprx.c:
+ Version tocall code: APRX21
+
+ * aprx.c, aprx.8.in:
+ Add '-i' option to keep the program foreground without
+ enabling any debug printouts.
+
+ * digipeater.c:
+ Correct the recognition of "probably heard on first hop"
+
+ * historydb.c:
+ Memory realloc() bug fixed.
+
+ * kiss.c:
+ Recognize a write attempt on closed file handle, don't write.
+
+2010-08-08 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * VERSION: 2.00
+ * aprx.c: Version tocall code: APRX20
+ * doc/aprx-manual.odt: version 2.00/1.00
+
+2010-08-02 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.c:
+ Version tocall code: APRX1M
+
+ * ROADMAP:
+ Planning update.
+
+ * beacon.c, aprx.8.in, aprx.conf.in, aprx-complex.conf.in:
+ OH7MMT complained difficulty of defining beacons on Rx-iGate.
+ The default aprx.conf shows the use of "interface ..", which
+ requires tx-enabled target interfaces. Something that rx-only
+ will not have.
+
+ * doc/aprx-manual.odt:
+ Cross references, additional notes on digipeat of non-APRS
+ packets.
+
+2010-07-27 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * config.c:
+ Accept valid APRSIS login callsigns for "mycall" parameter
+ value. Will complain latter if end usage did want valid AX.25
+ callsigns instead.
+
+2010-06-25 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * interface.c:
+ Account the number of created interfaces and subinterfaces,
+ and if there is just one and no callsign has been defined,
+ only then supply a default value ("callsign $mycall").
+
+ * interface.c:
+ Check interface callsigns to be unique.
+
+ * aprsis.c, beacon.c, digipeater.c, interface.c, telemetry.c:
+ Recognize "$mycall" in case insensitive matching.
+
+2010-06-22 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.c:
+ Version tocall code: APRX1L
+
+ * digipeater.c:
+ If there is a viscous-delay defined, add a random value of 0..2
+ to the configured number of seconds delay.
+ This way similarly configured adjacent Tx-iGates can at random
+ find a first transmitter and others will drop the Tx-iGate.
+
+ * digipeater.c:
+ Digipeater didn't detect interface callsigns and aliases properly,
+ when determining if there is some work to be done. It did detect
+ them when to avoid doing duplicate work.
+
+2010-06-19 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.c, aprx.h, aprsis.c, beacon.c, config.c, digipeater.c,
+ interface.c, telemetry.c, ttyreader.c:
+ Improve config file parsing error reporting.
+ Any ERROR observed on configuration parsing causes immediate
+ abort with exit-code 1.
+
+ * aprx.conf.in, aprx-complex.conf.in, aprx.8.in:
+ Clarify the documentation at man-page, and at config templates.
+
+ * firmware/*
+ Copied one source of TNC2 firmwares
+
+ * windows/*
+ Just a placeholder.
+
+ * Makefile.in:
+ Improving "make dist" behaviour.
+
+2010-06-13 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.8.in, doc/aprx-manual.odt:
+ Some notes on available baud-rates, fixes on
+ documentation about CRC algorithms.
+
+ * ttyreader.c:
+ Missed baud speed 57600.
+
+ * dupecheck.c:
+ Shrink the arena allocation size to 4 kB. This allows
+ 14 reference packets in the dupecheck dataset within that
+ arena alone, and will possibly request second arena when
+ this get really busy.
+
+ * historydb.c, dupecheck.c, pbuf.c, filter.c:
+ Made cell object sizes readable with a debugger
+
+ * crc.c:
+ Object visibility adjustments.
+
+ * Makefile.in, crc.c, aprx.h, kiss.c, ttyreader.c, interface.c:
+ Separated KISS processing to kiss.c, and CRC processing
+ to crc.c. Disabled unused codes.
+
+ * pbuf.c, historydb.c, dupecheck.c:
+ Shrink RAM usage, cellmalloc() does perfectly good job
+ of using free-chains, no need to do it in applications.
+
+ * interface.c, pbuf.c:
+ The pbuf_new() has a limit on maximum total size that
+ a packet can be: 2150 bytes. If ax25len + tnc2len are
+ more than that, pbuf_new() returns NULL.
+
+ * historydb.c, historydb.h:
+ Instead of historydb instance specific history entry pools,
+ have one global pool.
+
+2010-06-12 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * cellmalloc.c:
+ Remove pthread_mutex_t usage, not needed in this application!
+ Conditionalize debugging code.
+
+ * pbuf.c, aprx.c, aprx.h:
+ Using cellmalloc() on pbuf storage. Support up to 2150 byte
+ received AX.25 frames. (Up to 7 pbufs of that size on 16 kB
+ cell arena .. and there never should be larger than around
+ 512 byte AX.25 frames.)
+
+ * dupecheck.c:
+ Shrink dupecheck to 16 kB per alloc arena (from 256 KB).
+ That should be enough for a very busy 1200 bps channel,
+ and probably even for a 9600 bps channel.
+
+ * filter.c:
+ Shrink filter cell storage to 4 kB per alloc arena (from 512 kB!)
+
+ * historydb.c:
+ Shrink historydb cell storage to 32 kB per alloc arena (from 128 kB)
+
+ * debian/aprx.init:
+ Pleasing init-script behaviour during package re-install
+
+ * telemetry.c, doc/aprx-manual.odt:
+ Stagger telemetry transmissions a bit.
+ All RF reported telemetry sources are reported at the same time,
+ but telemetry data is time-wise separated from labels, and labels
+ are sent so that 3 different labels are sent every 2 hours, and
+ total label carouselle time is 6 hours.
+
+ * aprx.c:
+ Giving this version code: APRX1K
+
+ * telemetry.c:
+ Correct AX.25 data frame for RF transmission
+
+ * telemetry.c, config.c, aprx.h, doc/aprx-manual.odt,
+ aprx.conf.in, aprx-complex.conf.in:
+ New <telemetry> config section, be able to tx
+ telemetry to RF ports.
+
+ * beacon.c:
+ Error reporting additions.
+
+ * erlang.c, aprx-stat.c, aprx.h, telemetry.c:
+ Use pre-processor conditionals to select only small
+ subset of storage and processing codes used for
+ erlang data in embedded mode.
+
+ * parse_aprs.c:
+ Compile warning silencing.
+
+2010-06-10 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * kiss.c, ttyreader.c, dprsgw.c, aprx.h:
+ Tracked specification origins of each of the three
+ different CRC-16 calculations that this code has.
+ What was called "crc_ccitt" was in fact something
+ totally different: the SMACK CRC16.
+
+2010-06-07 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * README, ROADMAP, TODO, INSTALL, PROTOCOLS:
+ Updates on plans and rough guides.
+
+ * TIMESTAMP-AT-APRSIS, timestamp.c:
+ A proposal to put about 1ms resolution timestamps
+ on APRSIS with full backwards compatibility.
+
+ * aprx.c:
+ Giving this version code: APRX1J
+
+ * dprsgw.c, digipeater.c, aprx.h, doc/aprx-manual.odt:
+ Clean DPRS Rx gateway code.
+
+ * interface.c:
+ Correct 3rd-party packet gating filter behaviour:
+ If there are _no_ filters, pass all packets thru.
+
+ * interface.c, aprx.h, dprsgw.c, igate.c:
+ Rate-limit source callsigns per source interface to
+ once per 30 seconds. RF iGate DPRS messages to APRS
+ as 3rd-party messages.
+
+2010-06-06 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * parse_aprs.c, dprsgw.c:
+ Rudimentary GPSxyz -> APRS symbol mapping for DPRS,
+ with GPSxyz overlay character where possible.
+ Also IDENT TEXT, if any.
+
+ * debian/aprx.init:
+ The "restart" command fails to work quite often.
+ The reason being "set -e"...
+
+ * aprx.h, igate.c, dprsgw.c, ttyreader.c, ax25.c:
+ igate_to_aprsis(... , STRICTAX25) -- new flag
+ to control source specific information about
+ TNC2 format address formats.
+
+ * parse_aprs.c:
+ Remove unnecessary double precission floating point math.
+ Use of 'float' is quite enough.
+
+ * debian/control:
+ Change package name a bit
+
+ * dprsgw.c:
+ Data collection code fixes
+
+ * telemetry.c:
+ Send first telemetry at 20.0 minutes after start.
+ This is related to defaulting to EMBEDDED operation mode.
+
+ * configure.in, aprx.h, erlang.c:
+ Change defaults so that System is always "--with-embedded",
+ and requires explicit "--with-erlangstorage" to compile with
+ a filesystem based backing storage codes.
+
+2010-06-04 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.h, dprsgw.c:
+ Couple steps onwards with DPRS Rx-IGate.
+
+2010-06-01 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.c:
+ Set software tocall to 'APRX1H'.
+
+ * interface.c:
+ Found a memory leak on AX.25 type packet receiver. Oops!
+ Ax.25 packets that were rejected by <digipeater> <source>'s
+ filters were just leaked, not disposed.
+
+ * historydb.c:
+ Allocate cell arenas in 128 kB blocks.
+
+ * digipeater.c:
+ Valgrind complained about uninitialized variables.
+ Default zero is not good enough...
+
+2010-05-30 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.h, digipeater.c:
+ Use 'float' for ratelimit variables. Permitting as low
+ rate definitions as 'once per 10 minutes.
+
+ * dprsgw.c:
+ XOR checksums of "GPS" mode packets. No Rx-iGate of them yet.
+
+2010-05-29 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * dprsgw.c, ttyreader.c, aprx.c, aprx.h:
+ Experimental DPRS receiver. Studying packet identification
+ from serial port datastream with Rx-IGate of GPS-A type DPRS
+ packets.
+
+ * interface.c, doc/aprx-manual.odt:
+ Effects of 'filter ...' on different <digipeater>
+ <source> sections are subtly different. See the manual!
+
+ * aprx.h, digipeater.c, doc/aprx-manual.odt, aprx.conf.in,
+ aprx-complex.conf.in:
+ Remodelled the ratelimiting to use 'token bucket filter':
+ ratelimit average burst
+ both values are packets per minute, and both default to 60.
+
+ * digipeater.c, interface.c:
+ Return adjunk filters to RF->RF digipeating.
+ They got lost a bit back with Tx-IGate needing
+ them placed a bit differently.
+
+ * man-to-html.sh:
+ groff has changed its -Tascii output a bit, and
+ its own HTML output is horrible...
+
+2010-05-27 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * parse_aprs.c:
+ Correct parse of message recipients.
+ Fix from Patrick Domack K3PDK.
+
+2010-05-26 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * debian/rules, rpm/aprx.spec.in, aprsis.c:
+ Fixes on pthread:ed aprsis implementation.
+ Default packaging for Debian and RPM to use --with-pthread.
+ Now it shows only one "process", and two threads.
+ This makes the program portable to uCLinux, and possibly
+ to Windows.
+
+ * dprsgw.c, aprx.h, ttyreader.c, Makefile.in, interface.c,
+ aprx.8.in, aprx.conf.in, aprx-complex.conf.in:
+ Add basic configuration and infra of DPRS-to-APRS gw.
+
+ * interface.c, igate.c:
+ Comment editing, adding Tx-IGate Rule 4, second half.
+ First half probably exists in igate..
+
+2010-05-25 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.c, README, TODO, VERSION:
+ Mark version 1.99, APRX1G
+
+2010-05-25 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * interface.c, filter.c:
+ Filtering logic change on 3rd-party digipeating.
+
+2010-05-24 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * interface.c, TODO:
+ Notes about missing bits for full Tx-IGate.
+
+ * historydb.c, historydb.h:
+ Keep knowledge of last coordinate on historydb.
+ Mark also last coordinate update time.
+
+ * aprx.h, pbuf.h, parse_aprs.c, interface.c, digipeater.c:
+ Tx-IGate processing details..
+
+2010-05-23 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.c, aprx.h, digipeater.c, filter.c, historydb.c, historydb.h,
+ igate.c, interface.c, parse_aprs.c:
+ Rearranged bits so that Tx-IGate's processing has transmitter
+ specific history database. Better (but yet a bit incomplete)
+ Tx-IGate filtering is in interface.c.
+
+ * kiss.c, aprx.8.in, aprx.conf.in, aprx-complex.conf.in,
+ netax25.c, aprx.h, ttyreader.c:
+ Add FLEXNET support. (Finnish HamDR speaks it by default!)
+
+ * interface.c:
+ Corrections on <kiss-subif> definitions, in particular
+ the aliases.
+ Fixed Tx-IGate workhorse interface_receive_3rdparty()'s
+ packet address header construction.
+
+ * digipeater.c, aprx.h:
+ APRSIS specific via-path parameter parsing fixes, additional
+ debugging outputs, config error detection outputs.
+
+ * netax25.c, ax25.c:
+ Debugging outputs.
+
+ * ttyreader.c, aprx.h:
+ Debugging outputs.
+ Special KISS subtype "RFCRC" - received KISS frames have
+ two RANDOM bytes at their end. Looks like CRC16, but is
+ random junk.
+
+ * erlang.c:
+ Debug printout.
+
+ * beacon.c:
+ When an interface has no callsign, do not send beacon to it.
+ (It could be tty master interface which has multiple KISS
+ sub-interfaces.)
+
+ * igate.c:
+ Parse packet to be tx-igated for acceptable address syntaxes.
+ Debug printouts.
+
+2010-05-16 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprsis.c, aprx.conf.in, aprx.8.in, doc/aprx-manual.odt:
+ Emphasize that <aprsis>'s login parameter is to be
+ set ONLY when it needs to be different than mycall
+ value. (But tolerate also '$mycall' alias.)
+
+ * aprx.c:
+ Update internal tocall default to 'APRX1F'.
+
+ * interface.c, digipeater.c:
+ Tx-iGate fixes for case where outgoing packet has no
+ VIA field(s). System saw that "requested hops = 0,
+ done hops = 0 -> no need to send out".
+
+ * parse_aprs.c:
+ Make sure that all things creating debug output are
+ followed by a "\n".
+
+ * igate.c:
+ Relax station callsign format rules inside a received
+ 3rd-party frame.
+
+2010-05-13 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * interface.c:
+ Under all situations, fill in pbuf_t->dstcall_end.
+
+ * debian/rules:
+ Do not strip resulting objects. We want debug symbols!
+
+ * digipeater.c:
+ Improve config error reporting.
+
+ * config.c:
+ Tolerate config file ending without a newline.
+
+2009-12-27 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.c, VERSION: 1.98
+
+ * rpm/aprx.spec, debian/docs, debian/control:
+ More documents into the packages
+
+ * beacon.c, aprx.8.in, aprx.conf.in, aprx-complex.conf.in,
+ doc/aprx-manual.odt, doc/aprx-manual.pdf:
+ Changes on beacon definition keywords to make them clearer
+ for a user. Old forms continue as silently accepted aliases.
+
+2009-12-27 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * VERSION: 1.97
+
+ * beacon.c, aprx.8.in, aprx.conf.in, aprx-complex.conf.in:
+ Never send to APRSIS interface.
+ Define 'object' and 'item' type named entities.
+ Uppercasify several of input fields.
+
+ * interface.c, digipeater.c, aprx.h:
+ On tx-igate behaviour, add configured via-path on outgoing
+ 3rd-party frames
+
+2009-12-16 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * beacon.c:
+ Reworked more.. beacons were sent only for "tx-ok true"
+ interfaces, while netbeacons are to be sent for all interfaces.
+
+2009-12-03 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * beacon.c:
+ Reworked things a bit, on how and with what content to
+ send beacons.
+
+ * netax25.c:
+ Valgrind pleasing
+
+ * telemetry.c, ttyreader.c, beacon.c, netax25.c, aprx.h:
+ Debug printouts.
+
+2009-12-01 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * interface.c:
+ Register tty KISS subinterface 0 only once.
+
+ * beacon.c:
+ When a file-beacon is defined, the message content is
+ no longer at bm->msg, instead at local variable msg.
+
+ * netax25.c, aprsis.c:
+ Pleasing valgrind.
+
+2009-11-26 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * parse_aprs.c:
+ Sometimes ran memchr() with bad length. Caused rare crash.
+
+ * digipeater.c, aprxpolls.c, netax25.c, aprsis.c, aprx.c, aprx.h:
+ Valgrind tested minor changes.
+
+2009-11-17 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.8.in:
+ Big rewrite.
+
+ * aprx.h, interface.c, digipeater.c, netax25.c:
+ Digipeater "relay-type directonly" mode.
+ Interface parsing error detection improvement.
+
+2009-11-08 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * VERSION 1.96
+
+ * beacon.c, aprx.8.in, aprx.conf.in, aprx-complex.conf.in:
+ Rewrote beacon transmission interval codes, and documents
+ of the beacon system.
+
+ * configure.in, Makefile.in:
+ Added --with-pthread option, rewrote the --with-embedded
+ option processing.
+
+ * aprsis.c, configure.in:
+ Optionally support pthreads(3) where available, this can be
+ important for uCLinux and perhaps Windows, where fork(2) is
+ not available.
+
+2009-11-07 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.c:
+ Software tocall identity: APRX1C
+
+ * cellmalloc.c:
+ Drop semaphores. Not used in this codebase.
+
+ * filter.c, aprx.h, parse_aprs.c, Makefile.in:
+ Port Aprsc's filter code to Aprx.
+
+ * debian/aprx.init, rpm/aprx.init:
+ Update Debian's version, sync the rpm's version
+
+ * telemetry.c:
+ Report every 20 minutes, but scale values telling 10 minute
+ traffic. Start sending telemetry PARM blocks from system
+ start.
+
+ * aprx.c:
+ Init configurations before starting erlang accounting subsystem.
+
+2009-11-02 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * VERSION 1.95
+
+ * aprx.c:
+ Software tocall identity: APRX1B
+
+ * telemetry.c:
+ Erlang reporting confusion fixed.
+
+2009-10-30 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * interface.c:
+ More logging of interface setups.
+
+ * beacon.c, aprx.8.in, aprx.conf.in, aprx-complex.conf.in
+ New type of beacon: file /path/to/file, the first line
+ in given file is read every time this beacon entry is
+ executed, and the text is sent out sans line end LF.
+ There are no quote processing available.
+ .
+ Validate latitude and longitude input, and complain if
+ validate failed.
+ .
+ Beacon timefixing on all message types that support it,
+ and only upon special "timefix" option set on the beacon.
+ .
+ Beacon construction from small parts supports 6 different
+ fundamental packet types with coordinates.
+
+ * config.c:
+ Make config parser debugging needs -ddd runtime options.
+
+ * telemetry.c:
+ Report higher of 10 minute integrated packet/byte counts in
+ the 20 minute account interval. This makes graphs smoother.
+
+2009-10-27 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.h, aprx.c, aprsis.c, beacon.c, netax25.c, ttyreader.c,
+ erlang.c, aprx-stat.c, igate.c:
+ Modify the printtime() to print millisecond resolution times.
+
+2009-10-26 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * beacon.c, config.c, aprx.h, aprx.conf.in, aprx-complex.conf.in,
+ aprx.8.in:
+ Added a 'beaconmode { aprsis | both | radio }' config option,
+ which says where the beacons are sent to.
+
+ * ROADMAP
+ Development roadmap for first digit of version number
+
+2009-10-25 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * digipeater.c, beacon.c, interface.c, aprx.h:
+ Put all beacons on digipeater's transmitter duplicate checker
+ storage. Now a beacon with WIDE2-2 path will not be repeated
+ by message originator. Re-organized beacon's code base.
+
+ * netax25.c, interface.c, aprx.h:
+ Use Link-Level mechanism to send arbitrary AX.25 packets to
+ desired devices. Added also a about once minute run code
+ that checks current AX.25 devices in the system, and maps
+ necessary ifindex:es to netdevices wanting to do IO.
+ And all parameters to 'netax25_sendto()' are const..
+
+ * aprx.h, aprx.c, aprx-stat.c, aprsis.c, erlang.c, igate.c:
+ Commonly used strftime() got put into printtime() function.
+
+ * VERSION 1.94
+
+ * interface.c:
+ That final part of <interface> thing (below) can only
+ be done, if this interface has non-null tty field.
+
+ * Makefile.in
+ "make valgrind" - helps debugging some screwups..
+
+ * digipeater.c:
+ Stop scanning the viscous queue, once time limit exceeds
+ current time.
+
+ * aprxpolls.c:
+ A screwup on allocations found with valgrind. Oops!
+
+ * netax25.c, aprsis.c, dupecheck.c, ttyreader.c, erlang.c,
+ aprx.c, aprx.h, ax25.c, beacon.c, igate.c, historydb.c,
+ valgrind.c, Makefile.in:
+ Code changes to clean valgrind outputs on nuisance messages.
+ And at least one bugfix for valgrind environment...
+
+2009-10-24 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * telemetry.c:
+ Changed telemetry channel 2: Erlangs from bytes_tx accounting.
+ Now there is separate channel load graph showing how much this
+ transmitter is affecting the channel.
+ Give also better label for channel 4: IGateDropRx
+
+ * rfbeacon.c -> beacon.c, aprx.h, aprx.c, config.c, Makefile.in:
+ Rename rfbeacon.c to beacon.c.
+
+ * interface.c:
+ As a final part of <interface> definition, check if
+ the default tnc-subid (0) has associated ttycallsign
+ defined, and netax25 pty not enabled. If so, create it.
+
+ * parse_aprs.c, interface.c, aprx.h:
+ Depending upon usage, either do not look inside 3rd-party
+ frame, or do look inside. For Tx-iGate call paths we need
+ the analysis!
+
+ * parse_aprs.c, interface.c, historydb.c, digipeater.c,
+ rfbeacon.c, dupecheck.c, igate.c:
+ Removed codes ignoring "trailing CRLF in APRS frame tail".
+ They kept causing more trouble than good. The parse_aprs.c
+ is anyway diverged from its origins a bit more than I would
+ like, so no need to maintain mess related on its original
+ environment.
+
+ * dupecheck.c, aprx.h:
+ Adjust dupecheck hash bucket size - 16 is quite sufficient
+ enough even for a very busy igate/digi!
+
+ * igate.c, aprx.h, ax25.c, ttyreader.c
+ Restructured 3rd-party frame rx-igate processing so that it
+ does not need to alter TNC2 format buffer in any way.
+ This caused some weird things to happen in digipeater on cases
+ of 3rd-party frames.
+ Also modified ax25-to-tnc2 UI-formatter so that there will
+ always be zero termination of output text string.
+
+ * interface.c, digipeater.c:
+ Small restructuring while debugging other things.
+
+ * parse_aprs.c:
+ Parse APRS 3rd-party frame content, and recognize real
+ 3rd-party frame for sure.
+
+2009-10-24 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * VERSION: 1.93
+
+ * ttyreader.c, erlang.c:
+ Feeding in generated random noise did raise a few errors
+ in KISS frame processing, and erlang accounting.
+ KISS tncid:s sometimes did not have a callsign associated
+ with them, and then erlang accounting blew up...
+
+ * interface.c, rfbeacon.c, ax25.c, aprx.h:
+ Beacons to radio interfaces, KISS works best, NET-AX.25 less
+ well -- transmits OK UI frames, transmit of arbitrary other
+ types of frames is necessary for generic digipeating.
+
+ * digipeater.c:
+ Add alias to config parameter 'transmit' -> 'transmitter'.
+
+ * netax25.c, config.c:
+ Removed unused parameter on netax25_addrxport().
+ Rebuilt netax25_sendto(), however it sends out only UI frames..
+ Removed pre-created tx_socket, and associated codes.
+
+ * configure.in:
+ Remove unused autoconf test for cfmakeraw()..
+
+2009-10-23 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * rfbeacon.c:
+ Fix the debug printout of beacon to be sent.
+
+ * aprx.h, config.c: readconfigline()
+ Keep the cf->linenum showing first source line number on
+ continued multiline. This helps a bit on debugging, and
+ similar things are already done when a (sub)group parser
+ is reporting where that subgroup begins in case of missing
+ parameters in a group.
+
+ * digipeater.c:
+ Validating received requests better. Now marking also probable
+ situations that can say "this came from originator to me directly"
+
+ * netbeacon.c, rfbeacon.c, config.c, aprx.h, aprx.c, aprsis.c,
+ Makefile.in:
+ Removing the old netbeacon code.
+
+2009-10-22 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.h, rfbeacon.c, interface.c:
+ Implemented new infra for sending APRS beacons to radio
+ interfaces. Incoming request will at first construct
+ the AX.25 header, then send that to physical interfaces.
+
+ * aprx.h, ax25.c, digipeater.c, interface.c, kiss.c, pbuf.h,
+ pbuf.c, netax25.c, parse_aprs.c, rfbeacon.c, ttyreader.c:
+ Changed all instances of 'unsigned char' to 'uint8_t'.
+
+2009-10-21 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * config.c:
+ Clean away some superflous debugs.
+
+ * digipeater.c:
+ Viscous mode debug printout crashes on SEGV.. Oops.
+ (without debug the bug does not hit.)
+
+ * README.ViscousDigipeater, digipeater.c:
+ Thinking about what are correct rules on Viscous Digipeat.
+
+ * digipeater.c:
+ On configuration, make sure that no two <source> definitions
+ within single <digipeater> definition have same <interface>:s.
+
+2009-10-20 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.h, digipeater.c:
+ Viscous digipeater special corner cases..
+
+ * aprx.h, config.c, aprx.conf.in, aprx-complex.conf.in
+ Interval parser for "timeout" and "interval" parameters
+ used in several places.
+
+ * config.c, aprx.conf.in, aprx-complex.conf.in:
+ Line continuation (the '\' character at the end of
+ input line - with possible whitespaces following it)
+ is now supported.
+ An interval parser is added; timeouts and intervals
+ can be defined in human readable way: 3m2s
+
+ * aprx.8.in:
+ Larger rewrite to bring it up to current version.
+
+ * aprsis.c, aprx.conf.in, aprx-complex.conf.in:
+ Reworked details of <aprsis> configuring, and internal
+ operational semantics. Most notable difference is on
+ possibility to define used filters in small fragments,
+ which the system will then catenate.
+
+ * aprx.h, digipeater.c, dupecheck.c:
+ Returned the viscous delay processing back to version
+ just before "simplified viscous delay and dupechecking".
+ It really needs to check delayed vs. immediate counts.
+
+2009-10-19 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * VERSION: 1.92
+
+ * digipeater.c, interface.c:
+ Clean variable naming on digipeater <source> subsystem.
+
+ * aprx.h, aprx.conf.in, aprx-complex.conf.in, igate.c:
+ Update documentation
+
+ * igate.c:
+ Reject packets with destcall=RXTLM-* from Tx-IGate.
+
+ * digipeater.c:
+ Simplify control flow when feeding to viscous queue vs.
+ when running the backend directly.
+
+ * aprx.h, digipeater.c, dupecheck.c, igate.c:
+ - Fixed 3rd party dupechecking
+ - Simplified viscous delay and dupechecking.
+ The dupechecking happens at the start of digipeater_backend(),
+ which is called after pbuf's have been subjected to a viscous
+ delay, if ever necessary. First arriving packet is digipeated,
+ if it has steps to do. If not, rest are still considered dupes,
+ and not digipeated.
+
+ * aprx.h, igate.c:
+ Make rflog() public function to be used by multiple parties.
+ Also mark on the log line the direction to which the packet
+ was going (R/T), and not only its source interface.
+
+ * ax25.c:
+ Moved igate_to_aprsis() call before interface_receive_ax25(),
+ and thus also log received packet on rf.log before it is sent
+ out anywhere.
+
+ * aprx.h, interface.c, digipeater.c, aprx.8.in, aprx.conf.in,
+ aprx-complex.conf.in, dupecheck.c:
+ Moved viscous-delay processor into digipeater's <source>
+ control area. Implemented Tx-IGate's packet re-formatting
+ rules on interface.c. Implemented viscous-delay processing
+ in digipeater. Added refcounting on dupe_record_t objects.
+
+ * digipeater.c, igate.c, config.c, aprx.h, aprx-complex.conf.in:
+ Moved regexp reject filters from old style setup at igate.c
+ to new style at digipeater.c. This will not be able to reject
+ trivially configurable things from Rx-IGate datastreams, but
+ it can be used to control digipeating.
+
+ * ax25.c:
+ Remove unnecessary debug printout.
+
+2009-10-18 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * VERSION: 1.91
+
+ * aprx.h, dupecheck.c, digipeater.c, pbuf.c, pbuf.h, aprsis.c,
+ igate.c, keyhash.c:
+ Digipeater local instance of dupechecker. The dupe-checker
+ does recursive analysis of APRS packets for 3rd party
+ frames, and dupecheck the innermost frame of them.
+ Cleaned the keyhash.c to contain only FVN-1a hasher.
+
+ * digipeater.c, interface.c, telemetry.c, aprx.h, ttyreader.c,
+ erlang.c, aprx-stat.c:
+ Start transmitting digipeated frames to ttyreader's KISS
+ output. netax25's similar interface is not tested.
+ Changed also the Erlang dataset format, and begun to
+ produce Tx packet counts on telemetry. Accounting
+ saves also tx byte counts, but they are not reported.
+
+ * aprx.h, interface.c, digipeater.c, pbuf.h, pbuf.c, ax25.c:
+ Pass the UI PID information all the way to interface,
+ where it can be matched against a list of PIDs that will
+ be treated alike APRS in digipeating.
+ Have two modes in digipeating: one with interface (and
+ aliases) as recognized work targets, and other with APRS
+ wide/trace tags in addition to interface (and aliases).
+
+ * aprx.h, ax25.c, interface.c, digipeater.c, aprx.conf.in,
+ aprx-complex.conf.in:
+ - Prefill AX.25 address field formatted ax25callsign
+ field on all <interface> datastructures. That comes
+ handy when doing TRACE processing.
+ - Put exact "callsigns" of "WIDE", "TRACE", and "RELAY"
+ into system as interface callsign aliases. When they
+ are present in the request path, substitute interface
+ callsign there with H-bit set.
+ - Update aprx.conf samples to how the system can be
+ configured.
+
+2009-10-17 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.h, digipeater.c, interface.c, pbuf.h, parse_aprs.c:
+ Digipeater preparation, system counts done and requested
+ distribution operations with source specific as well as
+ transmitter specific keywords.
+
+ * aprx.h, ax25.c, interface.c, aprsis.c, pbuf.c, parse_aprs.c:
+ Changed a few "parse failed" returns to "parse OK".
+ Systematic feed of datapacket "with TNC2 line end CRLF pair".
+
+2009-10-16 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * erlang.c, aprx.h, ttyreader.c, netax25.c, igate.c:
+ Clean Erlang accountig API. Removed unused parameters.
+
+ * interface.c, aprsis.c, igate.c, aprx.h, aprx.c, digipeater.c:
+ Minimal <digipeater> <source> definition parsers.
+ Additionally internal "APRSIS" pseudo-interface.
+
+2009-10-15 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.h, aprx.c, rfbeacon.c:
+ Add internal "tocall" constant with value "APRX19".
+ That one will track software versions.
+ The 'for' keyword on beacons defaults to $mycall.
+
+ * aprx.h, aprx.c, interface.c, config.c, rfbeacon.c, aprsis.c,
+ ttyreader.c, netbeacon.c:
+ Config machinery redone towards new style. Rx-IGate
+ works again. (Old config should work too.)
+
+ * aprx.h, pbuf.c, pbuf.h, interface.c, digipeater.c, aprx.c:
+ Skeletons for pbufs, digipeaters, and their uses in
+ the interface layer.
+
+ * aprsdigi.c, ax25.c, Makefile.in:
+ Remove obsolete placeholder. Things will be done differently.
+
+ * config.c, rfbeacon.c:
+ The rfbeacon code will be doing all variants of beacons..
+
+ * interface.c, netax25.c, ttyreader.c, aprx.h, ax25.c:
+ interface_transmit_ax25() is able to transmit a fully formed
+ AX.25 header+control+body frame to Linux internal AX.25 network
+ devices as well as to any serial port attached KISS device.
+ Transmission to TNC2 devices is not supported.
+ Code is also very careful on checking the AX.25 frame header
+ structure, and rejecting outright any with invalid header
+ structure (bad continuation flags, bad chars in callsigns,
+ not so careful with SSID byte contents - too many are careless
+ with those :-( )
+
+2009-10-14 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * rfbeacon.c, interface.c, Makefile.in, config.c, aprx.c,
+ aprx.h, aprx.conf.in, aprsis.c, netbeacon.c:
+ Add a stub of rfbeacon.
+
+2009-10-13 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * interface.c, config.c, netax25.c, ttyreader.c, ax25.c,
+ aprx.h, ChangeLog:
+ Feed received AX.25 frames to interface layer for possible
+ digipeat processing. (Missing: APRSIS originated frames!)
+
+ * interface.c, ttyreader.c, aprx.h, aprx.conf.in, aprx.8.in:
+ Remodelled serial-device definitions into interface layer.
+ Documentation updates to match new system.
+
+2009-10-13 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprsis.c, aprx.h, config.c, netbeacon.c, aprx.conf.in,
+ VERSION, README, INSTALL, TODO:
+ <aprsis> interface config with new style entry.
+ More of <interface> definitions.
+ Experiments at aprx.conf.in writing.
+
+
+ * Makefile.in, aprx.8.in, aprx.conf.in, aprx.h, config.c,
+ filter.c, interface.c, netax25.c, netbeacon.c, pbuf.c,
+ pbuf.h, ttyreader.c:
+ Incremental work on new style of configurations
+ as outlined in the Requirement Specification document.
+ Old style configurations do still work.
+ Serial port initstring is now binary transparent.
+
+2009-10-05 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * VERSION: 1.06
+
+ * netbeacon.c:
+ Complete beacon coordinate validator code, now it
+ can detect invalid input values properly.
+
+ * netax25.c, aprsis.c, ttyreader.c, igate.c, aprx.h:
+ Cleaning gcc -Wall warnings on various platforms,
+ including OpenBSD.
+
+2009-10-01 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * VERSION: 1.05
+
+ * kiss.c, netax25.c, ttyreader.c, aprx.h, ax25.c:
+ Write the SMACK frame with correctly escaped CRC.
+ Fixed also serial-port initstring handling.
+
+2009-09-30 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * VERSION: 1.04
+
+ * netax25.c:
+ When writing AX.25 KISS frame to kernel, try to do it
+ up to 3 times. Also add some debug statements on
+ ax25-rxport processing.
+
+ * VERSION: 1.03
+
+ * ttyreader.c:
+ Fix on serial port startup - always turn on flows on
+ the port, and explicitely flush the driver level
+ buffers discarding possibly accumulated data.
+
+2009-09-28 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * VERSION: 1.02
+
+ * erlang.c, aprx-stat.c, aprx.h:
+ Remove subport from erlang codes, having "tncid N" on serial
+ port definitions takes care of this kind of things.
+
+ * kiss.c, ttyreader.c, netax25.c, ax25.h:
+ Moved KISS/SMACK encoder to separate module, the CRC16 calculator
+ went there as well. For each ttyreader sub-tncid there is
+ separately opened KISS-pty channel on Linux systems with given
+ callsign as interface's writer channel. The netax25 ax25-port
+ reader does not accept packets with source callsign as any of
+ our ttyreader callsigns.
+
+ * erlang.c:
+ Fix the erlang_find() to really find the interface call.
+
+2009-09-07 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * cellmalloc.c, netax25.c, keyhash.c:
+ Compiling at OS/X found a few odd problems, corrected.
+
+2009-08-30 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.8.in, aprx.conf.in, config.c, aprx.h, aprx.c, telemetry.c,
+ netax25.c, netbeacon.c, aprsis.c, ttyreader.c, erlang.c,
+ aprx-stat.c:
+ Rename the "mycall" configuration parameter to "aprsis-login",
+ what it really is being used at. There is no "mycall", anywhere!
+
+ * dupecheck.c, aprx.h, aprx.c, [dupecheck.h]:
+ Removed dupecheck.h after incorporating it into aprx.h.
+ Added the poll-interfaces to handle housekeeping operations.
+
+ * beacon.c -> netbeacon.c, Makefile.in, aprsis.c, aprx.c, aprx.h:
+ Change file name, and all references therein.
+ A preparation for separate RF beacons.
+
+ * netbeacon.c, aprx.8.in, aprx.conf.in:
+ Add "netbeacon dest APRS via NOGATE ..." options for configuration.
+
+ * netbeacon.c:
+ Use float math to determine next event times for all beacons
+ for smoother distribution.
+
+2009-08-29 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * Makefile.in, dupecheck.c, dupecheck.h, aprx.c, aprx.h, aprsis.c:
+ Add infrastructure for future: dupecheck()
+
+ * telemetry.c:
+ Add "NOGATE" on telemetry messages transmitted to APRSIS.
+
+ * igate.c:
+ Deeper look into Rx-IGate specs revealed couple missing details.
+ More bits towards Tx-IGate.
+
+2009-08-23 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * VERSION, INSTALL:
+ Mark version as: 1.00
+
+ * aprx.c:
+ Document '-L' option.
+
+ * aprx.h, aprsis.c, beacon.c, igate.c:
+ New internal API to pass data from aprx proper, and APRS-IS
+ communicator. This is able to carry binary (including NUL
+ bytes) data both on received AX.25 address, and frame content.
+
+ * telemetry.c:
+ Change a bit on information texts, and transmit frequency.
+
+ * aprx.conf.in, aprx.8.in:
+ Edit prototype configurations, and documentation
+
+ * aprx.c, aprx.h, igate.c, cellmalloc.h, historydb.h, historydb.c,
+ keyhash.h, keyhash.c, pbuf.h, parse_aprs.c:
+ Preparing infrastructure for TX capable i-gate, and digipeater.
+
+2009-02-10 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * Makefile.in, aprsdigi.c, igate.c, aprx.h, ax25.c, ttyreader.c,
+ netax25.c, aprsis.c:
+ Move rx-igate code to igate.c,and make initial moves to
+ collect information about what to do for tx-igate.
+
+ * PROTOCOLS, TODO, README:
+ Document updates.
+
+2008-12-07 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.c, aprsis.c, netax25.c, configure.in, ttyreader.c, Makefile.in:
+ Compile testing to get this to work on Solaris 10.
+ Also fixes on PIPE failure handling (correct SIGPIPE ignoring)
+ on platforms other than Linux -- and possibly also for Linux.
+ Now this should drop in to FreeBSD and OS/X as well.
+
+2008-10-28 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * netax25.c, aprx.8.in:
+ Turned upside-down the meaning of ax25-rxport config
+ parameter. There is no longer a wild-card receiving
+ mode in Linux internal AX.25 network receiving.
+ All APRS receiving interface callsigns must be listed
+ explicitely.
+
+ * netax25.c, ttyreader.c, aprx.h:
+ SMACK probe transmits on link that is configured for it.
+ Also offers some debug messages on SMACK activation.
+
+ * beacon.c, ax25.c:
+ Cleaning debug printouts
+
+2008-07-18 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * config.c:
+ Function for validate of callsign input syntax
+
+ * telemetry.c:
+ Please the compiler a bit, increment the telemetry
+ sequence number only after all telemetered interfaces
+ have been reported.
+
+ * aprsis.c:
+ If there is need to reconnect to APRSIS, pick all possible
+ IP addresses for it, and use them all. Also improved
+ the use of new IP resolver API a bit.
+
+ * ttyreader.c:
+ Explicitely set "KISSSTATE_SYNCHUNT = 0" in enumeration.
+ Memory blocks are created with memset() call clearing them.
+
+ * aprx.h, beacon.c:
+ Compiler pleasing
+
+ * erlang.c:
+ One-off array size handling, resulted in SEGV...
+
+2008-04-11 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * VERSION:
+ 0.22:
+
+ * erlang.c:
+ Auto-embed the erlang-dataset if backing-store open fails.
+
+ * ttyreader.c:
+ Mark closed socket as closed.
+
+2008-03-29 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * VERSION:
+ 0.21
+
+ * telemetry.c:
+ New "send Erlang data as telemetry packets to APRS-IS"
+ subsystem.
+
+ * aprsis.c:
+ Fix the APRS-IS network login protocol. There are TWO
+ parameter strings after the "vers" keyword.
+
+ * configure.in, Makefile.in, erlang.c:
+ Support compilation as embedded target. Then the erlang
+ datasets are not off-loadable to the memory mapped files,
+ rather they are very small in-memory tables.
+
+ * erlang.c, aprx.h, telemetry.c, ax25.c, aprx-stat.c:
+ Support for telemetry sending out info on received packets.
+
+ * beacon.c:
+ Improve input validation.
+
+2008-02-18 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * VERSION: aprx-0.18
+
+ * aprx.8.in, aprx.conf.in, beacon.c:
+ New syntax to define netbeacons. Also support older methods.
+
+ * config.c:
+ Bug in config_SKIPTEXT quoted string termination scanning.
+
+ * netax25.c, aprsis.c, aprx.h:
+ Removing dead code, hooks for future "TNC2 -> AX.25"
+
+ * ttyreader.c:
+ Send received KISS frames to system internal AX.25 network,
+ if host has such (such as Linux)
+
+2008-02-03 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * configure.in, netax25.c,
+ autodetect header <pty.h>, and libutil function openpty().
+
+ * netax25.c, aprx.h, aprx.c:
+ Rearranged netax25 module initing - to happen _latter_.
+
+ * ttyreader.c, aprx.h, netax25.c:
+ kissencoder() function
+
+ * ttyreader.c, aprx.h, netax25.c:
+ On Linux, use openpty() to create an AX.25pseudo-device on
+ which we then can push AX.25 format packets received from
+ non-AX.25 interfaces. This will itself also _ignore_ packets
+ received from this created interface.
+
+ * beacon.c:
+ "for" attribute for beacon messages, thus this system
+ can claim to be sending the beacons on behalf of others.
+
+ * ax25.c, aprx.h:
+ parse_ax25addr() function.
+
+ * config.c, aprx.8.in, aprx.conf.in:
+ The mycall parameter must be all uppercase AX.25 valid
+ callsign, and must not be same as any other callsign
+ in system internal AX.25 network. (This is meaningful
+ only on Linux systems..)
+
+ * Makefile.in:
+ Just some cleanup
+
+2008-01-30 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprsis.c:
+ Spotted watchdog doing reconnects every 2 minutes
+ (like it is supposed to be), and realized that
+ it really should not care what the server says,
+ just that server is saying something...
+
+ * erlang.c:
+ A bug in backing-store map protection logic.
+
+ * aprx.c:
+ See if pidfile exists. If it does and the start
+ is not for foreground, refuse to run if process
+ given in the pidfile does exist.
+
+ * aprx.8.in, config.c, netax25.c, ttyreader.c, aprsis.c,
+ aprx.h, ax25.c, aprx.conf.in, beacon.c:
+ New "callsign" config parameter for "radio serial" ports.
+ messages received from given port are sent out using that
+ callsign. Revamp the whole config parsing.
+
+ * aprsis.c:
+ Smarter main-loop to aprs-is loop message pass preparation
+ and usage codes -- pass a struct block as message leader.
+
+ * ttyreader.c, netax25.c, aprsis, ax25.c, beacon.c, aprx.h:
+ Pass explicite parameter towards the APRSIS telling, which
+ callsign (radio receiver) the message came in from.
+
+ * aprx.h, config.c, ttyreader.c, beacon.c:
+ Rework config parameter line parsing. Just one param is
+ processed by the main config reader loop, any further are
+ tasks of the individual subroutines (ttyreader, beacon)
+
+
+2008-01-26 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * erlang.c:
+ Properly handle r/o share mapping of the aprx.state erlang
+ dataset. This mode is used by the aprx-stat program.
+
+ * netax25.c, aprx.conf.in, aprx.8.in:
+ For each AX.25 socket received packet, query reception
+ interface address and reformat it to TNC2 format.
+ Use that for reception reports, logs, filters etc.
+ Reason: port names given in /etc/ax25/axports do not
+ persist at all! Even clean system boot may yield
+ different port names than what the file lists.
+
+ * Version 0.17
+
+ * INSTALL, README:
+ Minor edits
+
+ * configure.in, Makefile.in, debian/rules, rpm/aprx.spec.in,
+ Makefile:
+ A bit more coherency on make system regarding linking.
+ Remove generated Makefile. (Keep generated configure !)
+
+ * config.c, ttyreader.c, aprx.conf.in, aprx.8.in:
+ Call the "serialport" now with name "radio".
+ (Also old one works, so old config does not break.)
+
+ * aprx.h, netax25.c, ax25.c, ttyreader.c:
+ Pass port name down to tnc2_rxgate() function where
+ it is used in rf.log outputs. A distributed multi-
+ receiver setup log is somewhat .. odd looking when
+ most receivers get same packet.
+
+ * logrotate.arpx.in:
+ Rotate weekly, and compress immediately.
+ Monthly turned out to be too much for "embedded"
+ OH1GSM-1 system.
+
+2008-01-12 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * configure.in, configure, debian/rules, rpm/aprx.spec.in,
+ rpm/aprx.init, Makefile, config.h.in:
+ More RPM / configure rework.
+
+ * configure.in, configure, Makefile.in, Makefile, config.h.in,
+ install-sh, rpm/aprx.spec.in, debian/rules:
+ Had to add minimalistic configure script into system for
+ the RPM multi-target compilation to work.
+
+ * Makefile, rpm/aprx.spec.in, rpm/aprx.default, rpm/aprx.init:
+ RPM package build framework, including init and logrotate -scripts
+
+2008-01-10 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * version 0.16
+
+ * erlang.c:
+ Make the logged data narrower - to usually fit in 80 char lines.
+
+ * aprx.c:
+ Always close STDIN from reading, and replace it with
+ a file handle opened on /dev/null.
+ When daemoning, close also STDOUT and STDERR, and
+ replace them with handles writing to /dev/null.
+ .. and do it as late as possible.
+
+ * aprx.h, aprx.c, config.c, erlang.c, aprx.8.in, aprx.conf.in,
+ aprx-stat.c:
+ Add (and document) option for logging erlang data on separate
+ file without any runtime options or need to divert stdout
+ or syslog anywhere.
+
+2008-01-08 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.h, ttyreader.c, erlang.c, netax25.c, aprx-stat.c:
+ When talking with multi-drop KISS, account each TNC
+ separately.
+
+2008-01-07 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * version 0.15
+
+ * erlang.c:
+ Do not double-open the state backingstore file
+
+ * aprsis.c:
+ Remember to close the opened log files.
+
+ * ttyreader.c:
+ Support case of _no_ serialports (reading only via AX.25 net)
+
+ * Makefile, logrotate.aprx.in:
+ Put aprx logs on /var/log/aprx/, and have monthly rotate.
+
+ * debian/*, Makefile:
+ Debian package building infrastructure
+
+ * aprx.8.in, aprx.conf.in:
+ A bit elaboration on how to add multiple entries of
+ aprsis-server, and serialport definitions.
+
+ * aprsis.c, ttyreader.c, aprx.8.in, aprx.conf.in:
+ Remove last vestiges of program having any hardwired limits
+ on number of anything. There are (of course) lots of
+ parameters that are singletons, but all multiples are
+ now unlimited (within memory limits..)
+
+ * Makefile:
+ "make install" does install the CFGFILE (aprx.conf)
+ if it does not have to overwrite the thing.
+ "make dist" is even more interesting beast..
+
+2008-01-06 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * version 0.14
+
+ * ttyreader.c, aprx.conf.in, aprx.8.in:
+ Support TNC2 monitor format on reception.
+
+ * ttyreader.c, aprx.conf.in, aprx.8.in:
+ Overload the "serialport" configuration option with a mechanism
+ to define a IP-literal addressable remote TCP port somewhere
+ with e.g. KISS TNC on it.
+
+2008-01-05 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * Makefile:
+ Radical revisioning, Best Current Practice
+
+ * aprx.c:
+ Write aprx.pid file in all cases, not only when starting
+ as daemon.
+
+ * version 0.13
+
+ * aprx.h, aprx.c, erlang.c, aprx-stat.c:
+ Modifications on shared memory segment head, stores
+ MYCALL, correct running process PID.
+
+ * INSTALL:
+ Some text fixes
+
+ * aprx-stat.c, aprx.h, erlang.c:
+ Report also server process PID on the SNMP dataset,
+ and time in seconds since it was started.
+
+ * ax25.c:
+ Verify TNC2 format APRS message's FROM>DEST,VIA,VIA:
+ callsigns to be of proper syntax.
+
+ * Makefile, aprx.8, aprx.8.in, aprx-stat.8, aprx-stat.8.in,
+ aprx.conf, aprx.conf.in:
+ Centralized a bit of configuring into Makefile, generating
+ files from *.in versions.
+
+ * aprxpolls.c:
+ Library function used everywhere, part of "eliminate fixed
+ size preallocations" -task.
+
+ * aprsis.c:
+ Debug logging improvements, parent death detection.
+ Preserved MAXAPRSIS setting - of 10 servers.
+
+ * ttyreader.c:
+ Unfix the number of TTYs, now can be as many as one wants.
+
+ * config.c, aprx.c, aprx.h, aprx.conf.in, aprx.8.in:
+ pidfile configuration parameter, and its usage
+
+2008-01-04 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * VERSION, arpx.c, Makefile, aprx.8, aprx.8.in,
+ aprx-stat.8, aprx-stat.8.in:
+ Define system version in VERSION file, have it stamped
+ on programs and packages.
+
+ * config.c:
+ The "aprxlog" and "rflog" parameters were interchanged
+ at some point.
+
+ * aprx.c, aprsis.c, beacon.c:
+ Complain loudly with -d or -v options on, and if the
+ configuration does not set global mycall parameter.
+ Do not refuse to run, though! This is perfectly valid
+ for things like Erlang-monitoring.
+
+2007-12-29 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * version aprx-v0.12
+
+ * PROTOCOLS:
+ Writeup of existing (and planned) protocols that this
+ software uses.
+
+ * ax25.c:
+ Correct processing of 3rd-party frames.
+ Also separate TNC2 formatted frame Rx-igateing rules
+ from AX.25-to-TNC2 format translation routine.
+
+ * ax25.c, beacon.c, aprsis.c:
+ Centralize the APRSIS communication line ending CRLF char pair
+ addition into common code, not distributed all over.
+
+2007-12-25 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * version aprx-v0.11
+
+ * TODO, README:
+ Cleanup
+
+ * config.c, netax25.c, aprx.8, aprx.conf:
+ Config option "ax25-rxport" - Limits acceptance of APRS
+ packets only from listed Linux AX.25 ports.
+
+ * ttyreader.c, aprx.conf, aprx.8:
+ Reworked a bit of the serialport config options.
+ Now initstring, and various KISS modes are configurable
+
+ * erlang.c:
+ Carefull approach on erlang-file opening, if file exists,
+ and is non-zero size, open it only if magics match.
+
+ * config.c:
+ config_SKIPTEXT() terminates all scanned strings with NUL byte,
+ and moves to byte following it, if the termination byte was not
+ a NUL byte originally.
+
+ * aprx-stat.c, aprx-stat.8:
+ Option -t to show timestamps differently.
+
+ * Makefile, man-to-html.sh:
+ Produce decent format HTML versions of the man-pages.
+
+ * config.c, erlang.c, aprx.8, aprx.conf:
+ Added "erlang-log1min" option to control 1 minute interval
+ Erlang sampling logging behaviour.
+
+ * Makefile:
+ Fix "make install" of man-pages
+
+ * version aprx-v0.10
+
+ * aprx-stat.c, aprx-stat.8, erlang.c, Makefile:
+ Statistics reporter tool.
+
+ * aprsis.c, config.c, aprx.8, aprx.conf:
+ Multiple aprsis-server config definitions are now
+ supported, and used in round-robin fashion.
+
+ * ax25.c, config.c, aprx.8, aprx.conf:
+ Config option ax25-filter
+
+ * ttyreader.c, erlang.c:
+ Magic channel capacity constant expressions updated..
+
+2007-12-23 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprx.c, aprx.h, aprsis.c, aprx.8, aprx.conf, ax25.c, erlang.c:
+ Multiple configuration file options, rf-log, and aprx-log -files,
+ related documentation.
+
+ * config.c, aprx.8:
+ Special quoted-string escape processing.
+
+ * version aprx-v0.08
+
+ * erlang.c, aprx.c, aprx.h:
+ Erlang data has now a mmap():ed filesystem based backing-store.
+ Erlang-data can be syslog()ed, and independently of that,
+ it can be printed on STDOUT. Default syslog facility is "NONE".
+
+ * Great Rename -- the thing is now called: aprx
+
+2007-12-06 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * aprsg.8, erlang.c, aprsg.c, netax25.c, ttyreader.c:
+ Erlang logging now uses syslog(3), unless explicitely told to use
+ output to stdout. Also the Erlang log format was altered a bit,
+ now it reports also number of packets in the interval.
+
+ * erlang.c, Makefile:
+ "make ERLANG1=1" compiles in also 1 minute erlang logging interval.
+
+ * aprsis.c:
+ Code refactored to put the APRS-IS communication into its own
+ fork()ed sub-process communicating via a socketpair() with the
+ main loop. Now reconnection time with the APRS-IS server does
+ not affect functionality of the main loop.
+
+2007-12-05 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi>
+
+ * ChangeLog
+ Opened for the first time. Version 0.06.
+
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..baba939
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,60 @@
+
+ INSTALL of APRX 2.08
+
+
+Pre-made binary package building system exists for Debian and
+Redhat/Fedora systems. See details at the end of this file.
+
+
+A rough-cut version of the installation instructions
+
+ 0) Prerequisites:
+ Does not need anything beside standard libc!
+ (In particular the Linux version does not need
+ libax25 / libax25-dev !)
+
+ 1) Start with ./configure --parameters
+ For a small memory system without writable /tmp
+ you have to use --with-embedded option.
+
+ 2) Cleanliness is good start:
+
+ $ make clean
+
+ 3) Compile the thing:
+
+ $ make
+
+ 4) There is automatic "install" as:
+
+ # make install
+
+ with several presumptions about directories fixed
+ into the Makefile (possibly some adjustments are
+ required, depending upon your environment.)
+
+ 5) Edit the configuration file to match your system:
+
+ # emacs /etc/aprx.conf
+
+ See the aprx(8) man-page for more info (man 8 aprx)
+
+ 6) Program startup scripts ("init-scripts") exist for
+ couple system environments, others may need manual
+ adapting.
+
+
+
+For Debian users wanting to compile themselves instead of using
+precompiled binaries:
+
+ $ make make-deb
+
+ # dpkg -i aprx_2.07-....deb
+
+
+For RedHat/Fedora users:
+
+ $ make make-rpm
+
+ # rpm -Uvh aprx-2.07.svn###-1.i386.rpm
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..db21adc
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2007-2014, Matti Aarnio
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of Matti Aarnio nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..1d358ea
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,226 @@
+#
+# APRX -- 2nd generation receive-only APRS-i-gate with
+# minimal requirement of esoteric facilities or
+# libraries of any kind beyond UNIX system libc.
+#
+# Note: This makefile uses features from GNU make
+
+# -------------------------------------------------------------------- #
+
+# target paths
+VARRUN= /var/run # directory for aprx.state and pid-file
+VARLOG= /var/log/aprx # directory for direct logfiles
+CFGFILE= /etc/aprx.conf # default configuration file
+SBINDIR= /usr/sbin # installation path for programs
+MANDIR= /usr/share/man # installation path for manual pages
+
+# -------------------------------------------------------------------- #
+
+srcdir = .
+
+
+
+PROF= # used by 'make profile'
+
+# Compiler and flags
+CC= gcc
+CFLAGS= -Wall -g -O2 -pthread
+
+# Linker and flags
+LD= gcc
+LDFLAGS= -Wall -g -O2 -z noexecstack $(PROF)
+datarootdir= ${prefix}/share
+
+INSTALL= $(srcdir)/install-sh
+INSTALL_PROGRAM=$(INSTALL) -m 755
+INSTALL_DATA= $(INSTALL) -m 644
+
+# -------------------------------------------------------------------- #
+# no user serviceable parts below
+# -------------------------------------------------------------------- #
+
+# strip extra whitespace from paths
+VARRUN:=$(strip $(VARRUN))
+VARLOG:=$(strip $(VARLOG))
+CFGFILE:=$(strip $(CFGFILE))
+SBINDIR:=$(strip $(SBINDIR))
+MANDIR:=$(strip $(MANDIR))
+
+# generate version strings
+
+VERSION = 2.08
+SVNVERSION = $(shell cat SVNVERSION)
+versionupdate := $(shell if [ "$(PKG_REV)-$(PKG_RELEASE)" != "-" ]; then echo "$(PKG_REV)-$(PKG_RELEASE)" > SVNVERSION; fi)
+
+# VERSION:=$(shell cat VERSION)
+# SVNVERSION_CMD:=$(shell which svnversion)
+# SVNVERSION:=$(shell if ${SVNVERSION_CMD} > /dev/null 2>&1 \&\& test -x ${SVNVERSION_CMD} -a \( -d .svn -o -d ../.svn -o -d ../../.svn \) ; then ${SVNVERSION_CMD} | tee SVNVERSION ; else cat SVNVERSION; fi)
+
+DATE:=$(shell date +"%Y %B %d")
+RFCDATE:=$(shell date +"%a, %d %b %Y %H:%M:%S %z")
+
+DEFS= -DAPRXVERSION="\"2.08r$(SVNVERSION)\"" \
+ -DVARRUN="\"$(VARRUN)\"" -DVARLOG="\"$(VARLOG)\"" \
+ -DCFGFILE="\"$(CFGFILE)\""
+
+# program names
+PROGAPRX= aprx
+PROGSTAT= $(PROGAPRX)-stat
+
+LIBS= -lrt -lutil -lm -pthread -lrt
+OBJSAPRX= aprx.o ttyreader.o ax25.o aprsis.o beacon.o config.o \
+ netax25.o erlang.o aprxpolls.o telemetry.o igate.o \
+ cellmalloc.o historydb.o keyhash.o parse_aprs.o \
+ dupecheck.o kiss.o interface.o pbuf.o digipeater.o \
+ valgrind.o filter.o dprsgw.o crc.o agwpesocket.o \
+ netresolver.o timercmp.o #ssl.o
+
+OBJSSTAT= erlang.o aprx-stat.o aprxpolls.o valgrind.o timercmp.o
+
+# man page sources, will be installed as $(PROGAPRX).8 / $(PROGSTAT).8
+MANAPRX := aprx.8
+MANSTAT := aprx-stat.8
+
+OBJS= $(OBJSAPRX) $(OBJSSTAT)
+MAN= $(MANAPRX) $(MANSTAT)
+
+# -------------------------------------------------------------------- #
+
+.PHONY: all
+all: $(PROGAPRX) $(PROGSTAT) man aprx.conf aprx-complex.conf
+
+valgrind:
+ @echo "Did you do 'make clean' before 'make valgrind' ?"
+ make all CFLAGS="${CFLAGS} -D_FOR_VALGRIND_"
+
+profile:
+ @echo "Did you do 'make clean' before 'make profile' ?"
+ make all PROF="-pg"
+
+
+$(PROGAPRX): $(OBJSAPRX) VERSION Makefile
+ $(LD) $(LDFLAGS) -o $@ $(OBJSAPRX) $(LIBS)
+
+$(PROGSTAT): $(OBJSSTAT) VERSION Makefile
+ $(LD) $(LDFLAGS) -o $@ $(OBJSSTAT) $(LIBS)
+
+.PHONY: man
+man: $(MAN)
+
+.PHONY: doc html pdf
+doc: html pdf
+pdf: $(MAN:=.pdf)
+html: $(MAN:=.html)
+
+# -------------------------------------------------------------------- #
+
+.PHONY: install install-deb
+install: all
+ $(INSTALL_PROGRAM) $(PROGAPRX) $(DESTDIR)$(SBINDIR)/$(PROGAPRX)
+ $(INSTALL_PROGRAM) $(PROGSTAT) $(DESTDIR)$(SBINDIR)/$(PROGSTAT)
+ $(INSTALL_DATA) $(MANAPRX) $(DESTDIR)$(MANDIR)/man8/$(PROGAPRX).8
+ $(INSTALL_DATA) $(MANSTAT) $(DESTDIR)$(MANDIR)/man8/$(PROGSTAT).8
+ if [ ! -f $(DESTDIR)$(CFGFILE) ] ; then \
+ $(INSTALL_DATA) aprx.conf $(DESTDIR)$(CFGFILE) ; \
+ else true ; fi
+
+.PHONY: clean
+clean:
+ rm -f $(PROGAPRX) $(PROGSTAT)
+ rm -f $(MAN) $(MAN:=.html) $(MAN:=.ps) $(MAN:=.pdf) \
+ rm -f aprx.conf logrotate.aprx
+ rm -f *~ *.o *.d
+
+.PHONY: distclean
+distclean: clean
+ rm -f config.log config.status config.h
+ rm -rf autom4te.cache *.log* doc/.~*#
+
+# -------------------------------------------------------------------- #
+
+%.o: %.c VERSION Makefile
+ $(CC) $(CFLAGS) $(PROF) $(DEFS) -c $<
+ @$(CC) -MM $(CFLAGS) $(PROF) $(DEFS) $< > $(@:.o=.d)
+
+$(MAN:=.html): %.html : %
+ sh man-to-html.sh $< > $@
+
+$(MAN:=.ps): %.ps : %
+ groff -man $< > $@
+
+$(MAN:=.pdf): %.pdf : %.ps
+ ps2pdf $<
+
+logrotate.aprx $(MAN) aprx-complex.conf aprx.conf: % : %.in VERSION Makefile
+ perl -ne "s{\@DATEVERSION\@}{$(VERSION) - $(DATE)}g; \
+ s{\@VARRUN\@}{$(VARRUN)}g; \
+ s{\@VARLOG\@}{$(VARLOG)}g; \
+ s{\@CFGFILE\@}{$(CFGFILE)}g; \
+ print;" \
+ < $< > $@
+
+# -------------------------------------------------------------------- #
+
+#
+# Following is for the original author only...
+#
+
+DISTVERSION:=aprx-$(VERSION).svn$(SVNVERSION)
+DISTTARGET:=../$(DISTVERSION)
+RPMVERSION:=$(shell echo "${DISTVERSION}" | sed -e 's/aprx-//')
+.PHONY: dist svnversion-test
+
+svnversion-test:
+ # Special for the source maintainer only..
+ @sh svnversion-test.sh $(SVNVERSION)
+
+dist: svnversion-test
+ if [ ! -d $(DISTTARGET) ] ; then \
+ mkdir $(DISTTARGET) ; \
+ fi
+ tar cf - --exclude-backups --exclude-vcs --exclude=windows --exclude=*.log* --exclude=*.conf . | (cd $(DISTTARGET) ; tar xf -)
+ echo "$(DISTVERSION)" > $(DISTTARGET)/VERSION
+ perl -ne "\$$ver = '$(DISTVERSION)'; \
+ \$$ver =~ tr/0-9.//cd; \
+ \$$ver .= '-1'; \
+ s{\@VERSION\@}{\$$ver}g; \
+ s{\@RFCDATE\@}{$(RFCDATE)}g; \
+ print;" \
+ < $(DISTTARGET)/debian/changelog.release \
+ > $(DISTTARGET)/debian/changelog
+ rm -f $(DISTTARGET)/debian/changelog.release
+ rm -f $(DISTTARGET)/aprx.spec
+ perl -ne "s{\@VERSION\@}{$(RPMVERSION)}g; \
+ s{\@DATE0\@}{$(DATE0)}g; \
+ print;" \
+ < $(DISTTARGET)/rpm/aprx.spec.in \
+ > $(DISTTARGET)/aprx.spec
+ rm -f $(DISTTARGET)/rpm/aprx.spec.in
+ make -C $(DISTTARGET) distclean
+ cd .. && \
+ tar czvf $(DISTVERSION).tar.gz $(DISTVERSION)
+
+# -------------------------------------------------------------------- #
+
+.PHONY: make-deb make-rpm
+
+make-deb:
+ if [ -f debian/changelog.release ] ; then \
+ perl -ne "\$$ver = '$(DISTVERSION)'; \
+ \$$ver =~ tr/0-9.//cd; \
+ \$$ver .= '-1'; \
+ s{\@VERSION\@}{\$$ver}g; \
+ s{\@RFCDATE\@}{$(RFCDATE)}g; \
+ print;" \
+ < debian/changelog.release \
+ > debian/changelog ; \
+ fi
+ dpkg-buildpackage -b -us -uc -rfakeroot
+
+make-rpm: # actually just a reminder of how to do it..
+ rpmbuild --target i386 -ta ../$(DISTVERSION).tar.gz
+
+# -------------------------------------------------------------------- #
+
+# include object depencies if available
+-include $(OBJS:.o=.d)
diff --git a/Makefile.in b/Makefile.in
new file mode 100644
index 0000000..aa33e84
--- /dev/null
+++ b/Makefile.in
@@ -0,0 +1,226 @@
+#
+# APRX -- 2nd generation receive-only APRS-i-gate with
+# minimal requirement of esoteric facilities or
+# libraries of any kind beyond UNIX system libc.
+#
+# Note: This makefile uses features from GNU make
+
+# -------------------------------------------------------------------- #
+
+# target paths
+VARRUN= /var/run # directory for aprx.state and pid-file
+VARLOG= /var/log/aprx # directory for direct logfiles
+CFGFILE= @sysconfdir@/aprx.conf # default configuration file
+SBINDIR= @sbindir@ # installation path for programs
+MANDIR= @mandir@ # installation path for manual pages
+
+# -------------------------------------------------------------------- #
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+ at SET_MAKE@
+
+PROF= # used by 'make profile'
+
+# Compiler and flags
+CC= @CC@
+CFLAGS= @CFLAGS@ @CCPTHREAD@
+
+# Linker and flags
+LD= @CC@
+LDFLAGS= @LDFLAGS@ $(PROF)
+datarootdir= @datarootdir@
+
+INSTALL= $(srcdir)/install-sh
+INSTALL_PROGRAM=$(INSTALL) -m 755
+INSTALL_DATA= $(INSTALL) -m 644
+
+# -------------------------------------------------------------------- #
+# no user serviceable parts below
+# -------------------------------------------------------------------- #
+
+# strip extra whitespace from paths
+VARRUN:=$(strip $(VARRUN))
+VARLOG:=$(strip $(VARLOG))
+CFGFILE:=$(strip $(CFGFILE))
+SBINDIR:=$(strip $(SBINDIR))
+MANDIR:=$(strip $(MANDIR))
+
+# generate version strings
+
+VERSION = @VERSION_STRING@
+SVNVERSION = $(shell cat SVNVERSION)
+versionupdate := $(shell if [ "$(PKG_REV)-$(PKG_RELEASE)" != "-" ]; then echo "$(PKG_REV)-$(PKG_RELEASE)" > SVNVERSION; fi)
+
+# VERSION:=$(shell cat VERSION)
+# SVNVERSION_CMD:=$(shell which svnversion)
+# SVNVERSION:=$(shell if ${SVNVERSION_CMD} > /dev/null 2>&1 \&\& test -x ${SVNVERSION_CMD} -a \( -d .svn -o -d ../.svn -o -d ../../.svn \) ; then ${SVNVERSION_CMD} | tee SVNVERSION ; else cat SVNVERSION; fi)
+
+DATE:=$(shell date +"%Y %B %d")
+RFCDATE:=$(shell date +"%a, %d %b %Y %H:%M:%S %z")
+
+DEFS= -DAPRXVERSION="\"@VERSION_STRING at r$(SVNVERSION)\"" \
+ -DVARRUN="\"$(VARRUN)\"" -DVARLOG="\"$(VARLOG)\"" \
+ -DCFGFILE="\"$(CFGFILE)\""
+
+# program names
+PROGAPRX= aprx
+PROGSTAT= $(PROGAPRX)-stat
+
+LIBS= @LIBS@ @LIBRESOLV@ @LIBSOCKET@ @LIBM@ @LIBPTHREAD@ @LIBGETADDRINFO@ @LIBRT@
+OBJSAPRX= aprx.o ttyreader.o ax25.o aprsis.o beacon.o config.o \
+ netax25.o erlang.o aprxpolls.o telemetry.o igate.o \
+ cellmalloc.o historydb.o keyhash.o parse_aprs.o \
+ dupecheck.o kiss.o interface.o pbuf.o digipeater.o \
+ valgrind.o filter.o dprsgw.o crc.o agwpesocket.o \
+ netresolver.o timercmp.o #ssl.o
+
+OBJSSTAT= erlang.o aprx-stat.o aprxpolls.o valgrind.o timercmp.o
+
+# man page sources, will be installed as $(PROGAPRX).8 / $(PROGSTAT).8
+MANAPRX := aprx.8
+MANSTAT := aprx-stat.8
+
+OBJS= $(OBJSAPRX) $(OBJSSTAT)
+MAN= $(MANAPRX) $(MANSTAT)
+
+# -------------------------------------------------------------------- #
+
+.PHONY: all
+all: $(PROGAPRX) $(PROGSTAT) man aprx.conf aprx-complex.conf
+
+valgrind:
+ @echo "Did you do 'make clean' before 'make valgrind' ?"
+ make all CFLAGS="${CFLAGS} -D_FOR_VALGRIND_"
+
+profile:
+ @echo "Did you do 'make clean' before 'make profile' ?"
+ make all PROF="-pg"
+
+
+$(PROGAPRX): $(OBJSAPRX) VERSION Makefile
+ $(LD) $(LDFLAGS) -o $@ $(OBJSAPRX) $(LIBS)
+
+$(PROGSTAT): $(OBJSSTAT) VERSION Makefile
+ $(LD) $(LDFLAGS) -o $@ $(OBJSSTAT) $(LIBS)
+
+.PHONY: man
+man: $(MAN)
+
+.PHONY: doc html pdf
+doc: html pdf
+pdf: $(MAN:=.pdf)
+html: $(MAN:=.html)
+
+# -------------------------------------------------------------------- #
+
+.PHONY: install install-deb
+install: all
+ $(INSTALL_PROGRAM) $(PROGAPRX) $(DESTDIR)$(SBINDIR)/$(PROGAPRX)
+ $(INSTALL_PROGRAM) $(PROGSTAT) $(DESTDIR)$(SBINDIR)/$(PROGSTAT)
+ $(INSTALL_DATA) $(MANAPRX) $(DESTDIR)$(MANDIR)/man8/$(PROGAPRX).8
+ $(INSTALL_DATA) $(MANSTAT) $(DESTDIR)$(MANDIR)/man8/$(PROGSTAT).8
+ if [ ! -f $(DESTDIR)$(CFGFILE) ] ; then \
+ $(INSTALL_DATA) aprx.conf $(DESTDIR)$(CFGFILE) ; \
+ else true ; fi
+
+.PHONY: clean
+clean:
+ rm -f $(PROGAPRX) $(PROGSTAT)
+ rm -f $(MAN) $(MAN:=.html) $(MAN:=.ps) $(MAN:=.pdf) \
+ rm -f aprx.conf logrotate.aprx
+ rm -f *~ *.o *.d
+
+.PHONY: distclean
+distclean: clean
+ rm -f config.log config.status config.h
+ rm -rf autom4te.cache *.log* doc/.~*#
+
+# -------------------------------------------------------------------- #
+
+%.o: %.c VERSION Makefile
+ $(CC) $(CFLAGS) $(PROF) $(DEFS) -c $<
+ @$(CC) -MM $(CFLAGS) $(PROF) $(DEFS) $< > $(@:.o=.d)
+
+$(MAN:=.html): %.html : %
+ sh man-to-html.sh $< > $@
+
+$(MAN:=.ps): %.ps : %
+ groff -man $< > $@
+
+$(MAN:=.pdf): %.pdf : %.ps
+ ps2pdf $<
+
+logrotate.aprx $(MAN) aprx-complex.conf aprx.conf: % : %.in VERSION Makefile
+ perl -ne "s{\@DATEVERSION\@}{$(VERSION) - $(DATE)}g; \
+ s{\@VARRUN\@}{$(VARRUN)}g; \
+ s{\@VARLOG\@}{$(VARLOG)}g; \
+ s{\@CFGFILE\@}{$(CFGFILE)}g; \
+ print;" \
+ < $< > $@
+
+# -------------------------------------------------------------------- #
+
+#
+# Following is for the original author only...
+#
+
+DISTVERSION:=aprx-$(VERSION).svn$(SVNVERSION)
+DISTTARGET:=../$(DISTVERSION)
+RPMVERSION:=$(shell echo "${DISTVERSION}" | sed -e 's/aprx-//')
+.PHONY: dist svnversion-test
+
+svnversion-test:
+ # Special for the source maintainer only..
+ @sh svnversion-test.sh $(SVNVERSION)
+
+dist: svnversion-test
+ if [ ! -d $(DISTTARGET) ] ; then \
+ mkdir $(DISTTARGET) ; \
+ fi
+ tar cf - --exclude-backups --exclude-vcs --exclude=windows --exclude=*.log* --exclude=*.conf . | (cd $(DISTTARGET) ; tar xf -)
+ echo "$(DISTVERSION)" > $(DISTTARGET)/VERSION
+ perl -ne "\$$ver = '$(DISTVERSION)'; \
+ \$$ver =~ tr/0-9.//cd; \
+ \$$ver .= '-1'; \
+ s{\@VERSION\@}{\$$ver}g; \
+ s{\@RFCDATE\@}{$(RFCDATE)}g; \
+ print;" \
+ < $(DISTTARGET)/debian/changelog.release \
+ > $(DISTTARGET)/debian/changelog
+ rm -f $(DISTTARGET)/debian/changelog.release
+ rm -f $(DISTTARGET)/aprx.spec
+ perl -ne "s{\@VERSION\@}{$(RPMVERSION)}g; \
+ s{\@DATE0\@}{$(DATE0)}g; \
+ print;" \
+ < $(DISTTARGET)/rpm/aprx.spec.in \
+ > $(DISTTARGET)/aprx.spec
+ rm -f $(DISTTARGET)/rpm/aprx.spec.in
+ make -C $(DISTTARGET) distclean
+ cd .. && \
+ tar czvf $(DISTVERSION).tar.gz $(DISTVERSION)
+
+# -------------------------------------------------------------------- #
+
+.PHONY: make-deb make-rpm
+
+make-deb:
+ if [ -f debian/changelog.release ] ; then \
+ perl -ne "\$$ver = '$(DISTVERSION)'; \
+ \$$ver =~ tr/0-9.//cd; \
+ \$$ver .= '-1'; \
+ s{\@VERSION\@}{\$$ver}g; \
+ s{\@RFCDATE\@}{$(RFCDATE)}g; \
+ print;" \
+ < debian/changelog.release \
+ > debian/changelog ; \
+ fi
+ dpkg-buildpackage -b -us -uc -rfakeroot
+
+make-rpm: # actually just a reminder of how to do it..
+ rpmbuild --target i386 -ta ../$(DISTVERSION).tar.gz
+
+# -------------------------------------------------------------------- #
+
+# include object depencies if available
+-include $(OBJS:.o=.d)
diff --git a/PROTOCOLS b/PROTOCOLS
new file mode 100644
index 0000000..51ea167
--- /dev/null
+++ b/PROTOCOLS
@@ -0,0 +1,258 @@
+
+ Protocols spoken by APRX
+
+There are a few protocols spoken by the APRX:
+
+ 1) APRS-IS interchange
+
+ 2) Proprietary monitoring via shared memory segment
+
+ 3) APRX-APRXC linkage
+
+
+APRS-IS interchange
+
+ This protocol is fairly simple.
+
+ http://wiki.ham.fi/IGate_properties
+
+
+
+ APRS iGate Common Properties
+
+ Communication is over TCP streams of text lines terminating
+ at CR+LF pair. No CR or LF is permitted inside the text
+ line, however.
+
+ APRS-IS user authentication:
+
+ user USERID pass PASSCODE vers VERS STRINGS \
+ filter FILTER STRINGS
+
+ where 'USERID' is uppercase callsign plus possible "-SSID" tail.
+ The 'PASSCODE' is calculated with semi-secret algorithm out of
+ the uppercase callsign characters without '-' and SSID tail.
+ The 'VERS STRINGS' are free to be anything, except string 'filter',
+ and the 'FILTER STRINGS' are explained in document:
+ http://www.aprs-is.net/ServerCmds.htm
+ http://www.aprs-is.net/javAPRSSrvr/javaprsfilter.htm
+
+
+ Messages:
+
+ Lines beginning with '#' character are line noise (usually
+ something that the server replies..)
+
+
+ After successfull login, communication carries "TNC2" format
+ APRS messages. Namely text encoding of AX.25 UI frames in
+ what became known as "TNC2 monitor style":
+
+ SOURCE>DESTIN:payload
+ SOURCE>DESTIN,VIA,VIA:payload
+
+ The SOURCE, DESTIN, and VIA fields are AX.25 address fields,
+ and have "-SSID" value annexed if the SSID is not zero.
+ Also in VIA-fields, if the "HAS BEEN DIGIPEATED" bit is set
+ (AX.25 v2 protocol feature) a star ('*') character is appended.
+ VIA-fields are separated by comma (',') from DESTIN, and each
+ other.
+
+ A double-colon (':') separates address data from payload.
+ The payload is passed _AS_IS_ without altering any message
+ content bytes, however ending at first CR or LF character
+ encountered in the packet.
+
+
+ APRS-RX iGate Basic Rules
+
+
+ Packets with source addresses: NOCALL*, N0CALL*, WIDE*, TRACE*,
+ TCP*, are dropped and not relayed to APRS-IS.
+
+ Packets with VIA addresses: RFONLY, NOGATE, TCPIP, TCPXX are not
+ to be relayed to APRS-IS. Sometimes the VIA fields have '*' on
+ their tails.
+
+ Packets with payload beginning with character '?' are not to be
+ relayed to APRS-IS.
+
+ Packets with payload beginning with character '}' are so called
+ 3rd-party frames, and they are to be re-processed starting from
+ character following the '}' character.
+
+
+ When packet is sent to APRS-IS, the address gets appended
+ either a "q-construct", or equivalent:
+
+ ,qAR,gatecallsign
+ ,gatecallsign,I
+
+ The "qAR" et.al. are explained at: http://www.aprs-is.net/q.aspx
+
+
+ APRS-TX iGate has additional rules:
+
+ All rules:
+ http://www.aprs-is.net/IGateDetails.aspx
+
+ Specifically forbidden to relay to RF is "qAX", maybe some others too.
+ (See: http://www.aprs-is.net/q.aspx)
+
+ If the packet VIA-path received from APRS-IS contains TCPXX,
+ NOGATE, RFONLY, then the packet is to be dropped, and not relayed
+ to radio network. (Note: TCPIP is permitted.)
+
+ Packets relayed from APRS-IS to radio must use so called 3rd-party
+ format. Signature is '}' character.
+
+ Rules on re-sending recently heard packets are a bit more complex,
+ and are covered adequately in above referenced document.
+
+
+
+
+
+Proprietary monitoring mechanism
+
+ There is a tool for monitoring channel activity.
+ See aprx-stat(8)
+
+
+
+APRX-APRXC linkage
+
+ (For a feature in planning...)
+
+
+
+ With introduction of APRX-Cluster (APRXC) mode, multiple APRX
+ instances are used as remote attachments to TNCs, and one central
+ system runs more complicated business logic deciding which messages
+ to pass where, what beacons to send, etc. The central business-logic
+ server runs also connection with APRS-IS, and all things that it
+ implies.
+
+ Actually the APRX-Cluster-Server is APRX program version with
+ embedded Perl for business logic. Some very flexibly configurable
+ APRS digi software has practical limitations of what the config
+ can do.
+
+ Environmental pre-requisite: APRXC and APRX nodes are in NTP sync!
+
+ The Protocol:
+
+ Communication is of text lines over TCP stream, they are canonic
+ Internet format ending with CRLF pairs. However no embedded CR,
+ nor LF is permitted.
+
+ (detail under study: UDP frames, or SCTP SEQPACKET ?)
+
+ The protocol is bidirectional, and is intended to be connected from
+ edge systems to cluster server. Upon receiving a connection the
+ server sends a greeting containing time-varying string. This will
+ be used by the connecting party to do authentication in APOP-style.
+
+ The timestamps in this protocol are "U" format:
+ sprintf(timestamp, "U%ld", (long)time(NULL));
+ which is to say, integer presentation of UNIX internal time in whole
+ seconds.
+
+
+ In following ">>" mean data sent by central system and received by
+ the edge, whereas "<<" means data sent by edge system to central
+ system.
+
+ << (connection formation from edge to the server)
+
+ >> <timestamp> Hello <++counter> some greeting string
+
+ << <timestamp> LOGIN <userid> <authenticator>
+
+ >> <timestamp> OK
+
+ << <timestamp> SERVICE <ifname> <speed> { RX | TX <areaspecifiers> }
+
+ >> <timestamp> OK
+
+ The <authenticator> is formed by hex (lower case) encoding of 16 byte
+ MD5() checksum over the whole greeting string (sans CRLF), plus userid,
+ plus shared plaintext password.
+
+ Replies can also be "FAIL", if "LOGIN" or "SERVICE" is somehow bad.
+
+ The <ifname> is local interface name in given node. The <speed> is
+ radio channel bitrate. The <areaspecifiers> are filter statements
+ in APRS-IS style.
+
+
+ The edge system compares every received timestamp (aside of the
+ login-sequence above) with its internal time reference, and if
+ the time varies more than 3 seconds from expected, the received
+ frame is discarded.
+
+ If APRXC Server notes that the time stamp it received is more than
+ 3 seconds in future or in past, it rejects the message.
+ (This does not apply to ERLANG verbs.)
+
+ The APRXC server acknowledges every received message with:
+
+ >> <timestamp> OK
+
+ Link idle jabber is by done by both systems by sending "TIME" verbs.
+ They are sent every 20 seconds since previous transmit of anything
+ on the connection, and if either notes that link has not received
+ anything for 120 seconds, the link is considered to be down, closed,
+ and re-initiated.
+
+ << <timestamp> TIME
+ >> <timestamp> OK
+
+ >> <timestamp> TIME
+ << <timestamp> OK
+
+ Either system can initiate the "heartbeat", sometimes even both
+ may send the TIME verbs at the same time, but both are not required
+ to do so.
+
+
+ The edge system sends received APRS frames to the central system
+ in "TNC2" format in a command:
+
+ << <timestamp> APRS <ifname> <TNC2frame>
+
+ The <TNC2frame> is the data received from radio as is without any
+ APRS-IS -type additional parameters inserted into address field.
+
+
+ When central system wants that a TX capable edge system transmits
+ something, it sends following message:
+
+ >> <timestamp> APRSTX <ifname> <TNC2frame>
+
+ the edge system will transmit the frame as is (the APRXC will format
+ the message to be ready for transmit.) If the edge system can not
+ transmit on given interface, it reports:
+
+ << <timestamp> FAILTX <ifname>
+
+ which results from APRXC and/or APRX software bug listing/flagging
+ tx-interface to be non-tx-interface.. Otherwise it acks the packet
+ with "OK" message.
+
+ << <timestamp> OK
+
+ Edge systems send 1 minute ERLANG data to central system with verb:
+
+ << <timestamp> ERLANG <ifname> <rxbytes> <rxpackets> \
+ <txbytes> <txpackets> [ <rxerlang> <txerlang> ]
+ >> <timestamp> OK
+
+ (Long line is folded in UNIX-style in this documentation.
+ Data is ACKed with OK.)
+
+ The APRXC collates these to 1 minute, 10 minute and 60 minute datasets
+ of given timestamp. The measured erlang-values are optional, if edge
+ system can produce truthfull ones.
+
+ The time-series collation database is persistent, for example RRD.
diff --git a/README b/README
new file mode 100644
index 0000000..8c130bc
--- /dev/null
+++ b/README
@@ -0,0 +1,86 @@
+
+ APRX v2.08
+
+A multitalented APRS / DPRS / APRSIS "i-gate" with following properties:
+
+ Config file (-f option) default is: /etc/aprx.conf
+ Other runtime options are: -v, -d, -h/-? (verbout, debug and help)
+
+ - Rx-IGate functionality works correctly
+ - Tx-IGate functionality works correctly
+
+ - Can do APRS New-N and generic AX.25 node digipeater functionality
+ with transmitters
+
+ - Has same-channel Viscous Digipeater functionality to not to digipeat
+ at all, if during initial wait period the packet is heard again.
+
+ - Has cross-interface Viscous Digipeater functionality to not to digipeat,
+ if during the initial wait period the packet is heard on destination
+ interface at least once, and at least once from other sources.
+
+ - Can receive data from multiple receivers/modems on local machine
+ serial ports, both classical and USB.
+
+ - Can receive data from remote TCP stream connectable serial ports
+ over the internet.
+
+ - Understands on serial ports (local and remote TCP ones):
+ - several KISS protocol variants, checksummed variants preferred
+ - TNC2 debug style text (Rx-iGate receive only.)
+ - D-STAR data side-channel "D-PRS"
+
+ - Connects with one callsign-ssid pair to APRS-IS core for all
+ received radio ports (the "mycall" parameter), but reports
+ receiving radio port at each Rx-iGated packet
+
+ - Knows that messages with following tokens in VIA fields of the
+ path are not to be relayed into network:
+ RFONLY, NOGATE, TCPIP, TCPXX
+
+ - Knows that following source address prefixes are bogus and thus
+ to be junked at Rx-iGate:
+ WIDE, RELAY, TRACE, TCPIP, TCPXX, NOCALL, N0CALL
+ (Actually these are string prefixes, so any WIDE*-* will block, etc.)
+
+ - Has integrated D-PRS -> APRS/APRSIS Rx-iGate.
+ Can even do D-PRS -> APRS RF conversion.
+ This is experimental quality for "GPS" packets, the "GPS-A" is OK.
+
+ - Does not require machine to have AX.25 protocol support internally!
+
+ - On Linux machine with kernel internal AX.25 protocol support, does
+ listen on internal AX.25 network in promiscuous form, and requires
+ to be running as root to do that. Does not fail to start in case
+ the port fails to open (running as non-root.)
+
+ - Built-in "erlang-monitor" actually counts bytes per time interval
+ (1 min, 10 min, and 20 min) on each receiving interface, including
+ all that feed internal AX.25 network.
+
+ - Telemetry reported erlang data is sent out every 20 minutes, and
+ contains summarized data from 10 minute round-robin memory arrays.
+ These are _not_ sent at exact 10 minutes of wall-clock, but exact
+ 20 minutes from previous telemetry reporting, and first one is sent
+ 20 minutes after program start.
+
+ - Telemetry reported erlang data can be sent also over APRS radio
+ port, but only for ports with valid AX.25 callsigns. See aprx-manual.pdf
+
+ - The netbeacons are distributed timewise more evenly around the interval,
+ and even the interval length is varied at random in between 20 and 30
+ minutes. Number of netbeacons are unlimited, but their minimum transmit
+ interval is 3 seconds making the amount of beacons sendable in 20 minutes
+ to be: 20*60/3 = 400. All will be sent, but the cycle will just take
+ longer.
+
+ - Source code is at SVN repository: http://repo.ham.fi/svn/aprx
+ - A Wiki page of this package: http://wiki.ham.fi/Aprx.en
+ - A google-group for Aprx: http://groups.google.com/group/aprx-software
+
+ - This program is also compilable as "EMBEDDED" target without
+ any statistics buffer required at the system disk (or RAM-disk).
+ See the INSTALL file.
+
+
+by Matti Aarnio - OH2MQK - oh2mqk-at-sral-fi - 2007-2014
diff --git a/ROADMAP b/ROADMAP
new file mode 100644
index 0000000..95f70c4
--- /dev/null
+++ b/ROADMAP
@@ -0,0 +1,26 @@
+ Aprx Roadmap and Future Directions
+
+
+Version 1
+ - APRS Rx-only iGate - Complete, working
+ - channel activity monitoring and telemetry - Complete, working
+
+
+Version 2
+ - Digipeater - Working
+ - Analyze and detect station distance - Working
+ - Radio beacons - Working
+ - Bidirection (Rx/Tx) APRS iGate - Working
+ - DPRS->APRS GW - Working
+
+Version 2+
+
+ - Port to ucLinux - Planned (pthread OK)
+
+ - Port to Windows
+
+ - Automated coverage statistics analyzer, and
+ reporting it via digi node identity beacons.
+ "ALOHA circles"
+
+ - Automated coverage plotting
diff --git a/SVNVERSION b/SVNVERSION
new file mode 100644
index 0000000..651c401
--- /dev/null
+++ b/SVNVERSION
@@ -0,0 +1 @@
+593
diff --git a/TIMESTAMP-AT-APRSIS b/TIMESTAMP-AT-APRSIS
new file mode 100644
index 0000000..9350b50
--- /dev/null
+++ b/TIMESTAMP-AT-APRSIS
@@ -0,0 +1,264 @@
+
+ A PROPOSAL FOR TIMESTAMPS AT APRSIS
+
+It can be taken as granted that today at least server systems are
+running with NTP service keeping their system clocks within a few
+milliseconds of UTC time.
+
+Systems utilized as APRS IGATE should be able to utilize NTP service
+as well, and keep their internal time well disciplined.
+
+SimpleNTP clients are available even for embedded system code bases
+making all kinds of internet capable systems able to have high quality
+time management.
+
+Timestamps are added on APRS text lines as prefixes of 8 characters.
+Eighth character is always ':'. Details are in "ENCODING" part.
+
+
+
+ TRANSPORT COMPATIBILITY
+
+As APRSIS clients would not know at first what to do with the timestamps,
+a transport compatibility must be introduced:
+
+ * APRSIS server announces on its connection message that it
+ is TIMESTAMP capable, and clients MUST NOT send timestamps
+ to APRSIS server without that capability
+
+ # telnet aprsisserver 14580
+ Connected to aprsisserver
+ Escape character is '^]'.
+ # javAPRSSrvr 4.0b1 [APRSIS TIMESTAMP]
+
+ Capability announcements follow style of IMAPv4 protocol,
+ where capability tokens are in square brackets, and are
+ separated by space character.
+
+ * If a client does not send timestamped data to APRSIS, APRSIS
+ server adds the timestamp upon receiving the line.
+
+ APRSIS server does this conditional timestamping also for packets
+ received via UDP for initial deployment compatibility.
+
+ * An APRSIS server connected to upstream with TCP is a client,
+ and must strip received/internal timestamps from packets sent to
+ upstream, if the upstream does not announce TIMESTAMP capability.
+
+ If upstream does not send timestamps for any reason, APRSIS server
+ adds it locally.
+
+ * A client that has not announced timestamp awareness does
+ not receive timestamps from APRSIS, and furthermore, APRSIS is
+ filtering all packets with maximum age of 15 seconds.
+
+ * A client that sends to APRSIS a text line with recognized
+ timestamp will get also timestampped lines sent up to itself.
+
+ No maximum age filtering is applied to clients that are timestamp
+ capable, and therefore those clients must be able to do timestamp
+ age analysis themselves.
+
+ Special timestamp-line "AAAAAAA:\n" tells APRSIS server, that
+ the client is APRSIS aware, and while it has nothing to actually
+ send to APRSIS at this time, it does want all data sent to it to
+ contain timestamps.
+
+
+APRSIS server software needs a configuration parameter
+
+ udptimestamps = <boolean>
+
+which defaults to "false". When the value is "false" the software will
+not send timestampped data to core peer machines over UDP.
+
+
+There probably is no need for an Adjunct Filter that knows about timestamps.
+
+
+
+ INITIAL DEPLOYMENT OF TIMESTAMP CAPABILITY
+
+Because APRSIS core uses UDP protocol, and thus can not be aware of remote
+node capabilities, the deployment of timestamp aware APRSIS servers at
+APRSIS core has two phases:
+
+ 1) Software has configuration parameter "udptimestamps"
+ non defined or defined as "false".
+
+ 2) After all core APRSIS systems are running timestamp
+ aware software, the configuration parameter can be
+ changed to "udptimestamps = true" and APRSIS server
+ starts to send timestamped packets over UDP to all of
+ its peers. --> peer machines do not need to locally
+ add timestamps at reception time.
+
+
+Everybody else can take TIMESTAMP capable system into use at any time
+that is convenient to them, unless communication involves UDP.
+
+
+ ENCODING
+
+Use NTP timestamp (see RFC 2030) based time abstraction:
+
+ Since NTP timestamps are cherished data and, in fact, represent the
+ main product of the protocol, a special timestamp format has been
+ established. NTP timestamps are represented as a 64-bit unsigned
+ fixed-point number, in seconds relative to 0h on 1 January 1900. The
+ integer part is in the first 32 bits and the fraction part in the
+ last 32 bits. In the fraction part, the non-significant low order can
+ be set to 0.
+
+Take highest 32+10 bits of the timestamp, and encode those in BASE64
+characters, most significant character first. This produces 7 encoded
+characters with time resolution of 1/1024 seconds. With 6 encoded
+characters the time resolution would be 1/16, which is felt to be
+too coarse.
+
+A hex encoded byte sequence of NTP timestamp looks like this:
+
+ cf b4 ff a4 b5 a8 8b f9
+
+it represent timestamp 3484745636.709603071 (2010/06/05 19:53:56)
+and encoded timestamp value is:
+
+ z7T/pLW
+
+
+The APRSIS passes around APRS messages as lines of text:
+
+ OH2JCQ>APX195,TCPIP*,qAC,T2FINLAND:=6013.63N/02445.59E-Jani
+
+Adding 7 character encoded timestamp + ":" character in the beginning
+of a line would make this:
+
+ z7T/pLW:OH2JCQ>APX195,TCPIP*,qAC,T2FINLAND:=6013.63N/02445.59E-Jani
+
+There 8th character is always ':', and preceding 7 bytes present _current_
+timestamp at 1/1024 second resolution.
+
+Timestamp value "AAAAAAA:" is "no timestamp" (decodes as time 0.0) and
+a client can use it to tell that it has no idea of timestamp, but it
+wants to receive timestamped data. An APRSIS data collector can enable
+timestamp reception by sending line: "AAAAAAA:\n".
+
+
+Packets with timestamps outside -1 to +N seconds limits are sign that
+communication is retrying too much, or otherwise delayed, and such packets
+are to be discarded. (Services like APRSFI, FINDU et.al. may want even
+older packets!)
+
+To be compatible with NTP Era roll-over, the timestamp must really be
+treated as 64-bit unsigned long integer, and compared with twos complement
+arithmetic using overflows.
+
+
+ TIMESTAMP ENCODING CODE EXAMPLE
+
+Example UNIX system C code to produce and encode a timestamp:
+
+ // Time Base Conversion Macros
+ //
+ // The NTP timebase is 00:00 Jan 1 1900. The local
+ // time base is 00:00 Jan 1 1970. Convert between
+ // these two by added or substracting 70 years
+ // worth of time. Note that 17 of these years were
+ // leap years.
+
+ #define TIME_BASEDIFF (((70U*365U + 17U) * 24U*3600U))
+ #define TIME_LOCAL_TO_NTP(t) ((t)+TIME_BASEDIFF)
+
+ void unix_tv_to_ntp(struct timeval *tv, uint64_t *ntp) {
+ // Reciprocal conversion of tv_usec to fractional NTP seconds
+ // Multiply tv_usec by ((2^64)/1_000_000) / (2^32)
+ uint64_t fract = 18446744073709ULL * (uint32_t)(tv->tv_usec);
+ // Scale it back by 32 bit positions
+ fract >>= 32;
+ // Place seconds on upper 32 bits
+ fract += ((uint64_t)TIME_LOCAL_TO_NTP(tv->tv_sec)) << 32;
+ // Deliver time to caller
+ *ntp = fract;
+ }
+
+ static const char *BASE64EncodingDictionary =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789"
+ "+/";
+
+ void encode_aprsis_ntptimestamp(uint64_t ntptime, char timestamp[8])
+ {
+ int i;
+
+ ntptime >>= 22; // scale to 1/1024 seconds
+ for (i = 6; i >= 0; --i) {
+ int n = (((int)ntptime) & 0x3F); // lowest 6 bits
+ // printf(" [n=%d]\n", n);
+ ntptime >>= 6;
+ timestamp[i] = BASE64EncodingDictionary[n];
+ }
+ timestamp[7] = 0;
+ }
+
+ static const int8_t BASE64DecodingDictionary[128] =
+ { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, // ' ', '!', '"', '#'
+ -1, -1, -1, -1, // '$', '%', '&'', '\''
+ -1, -1, -1, 62, // '(', ')', '*', '+',
+ -1, -1, -1, 63, // ',', '-', '.', '/'
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // '0' .. '9'
+ -1, -1, -1, -1, -1, -1, // ':', ';', '<', '=', '>', '?'
+ -1, 0, 1, 2, 3, 4, 5, 6, // '@', 'A' .. 'G'
+ 7, 8, 9, 10, 11, 12, 13, 14, // 'H' .. 'O'
+ 15, 16, 17, 18, 19, 20, 21, 22, // 'P' .. 'W'
+ 23, 24, 25, -1, -1, -1, -1, -1, // 'X'..'Z', '[', '\\', ']', '^', '_'
+ -1, 26, 27, 28, 29, 30, 31, 32, // '`', 'a' .. 'g'
+ 33, 34, 35, 36, 37, 38, 39, 40, // 'h' .. 'o'
+ 41, 42, 43, 44, 45, 46, 47, 48, // 'p' .. 'w'
+ 49, 50, 51, -1, -1, -1, -1, -1 }; // 'x'..'z', ...
+
+
+ int decode_aprsis_ntptimestamp(char timestamp[8], uint64_t *ntptimep)
+ {
+ uint64_t ntptime = 0;
+
+ int i, n;
+ char c;
+
+ for (i = 0; i < 7; ++i) {
+ c = timestamp[i];
+ if (c <= 0 || c > 127) return -1; // BARF!
+ n = BASE64DecodingDictionary[(int)c];
+ // printf(" [n=%d]\n", n);
+ if (n < 0) {
+ // Should not happen!
+ return -1; // Decode fail!
+ }
+
+ ntptime <<= 6;
+ ntptime |= n;
+ }
+ ntptime <<= 22;
+ *ntptimep = ntptime;
+ return 0; // Decode OK
+ }
+
+
+You may choose to produce timestamps with coarser resolution than 1/1024
+seconds. Let least significant bits to be zero. The about 1 millisecond
+resolution is a compromise in between 1 second, and sub-nanosecond
+resolutions.
+
+A heavy producer and consumer of timestamps, like an APRSIS server, may have
+single thread waking up 10 times per second and producing a new timestamp
+object every time, and overwriting global storage pointer to "current" one.
+For access there is no need to do any sort of synchronizations.
+Then received messages just copy that timestamp if necessary, and not run
+its production by themselves.
+
+Note: There is no need to convert NTP timestamps to local time in this
+application. It is quite enough that system receiving NTP timestamps
+does regularly create current reference NTP timestamp to be able to
+determine offset from received timestamp to reference time.
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..81f5824
--- /dev/null
+++ b/TODO
@@ -0,0 +1,109 @@
+ APRX 2.08
+
+ TODO / Wishlist
+
+- "[Aprx] Re: WIDE2-2/NOGATE - trace not working?"
+
+- "[Aprx] Re: Bi-Directional Cross-band Digipeater"
+ Probably heard sources accounting bucket initializing as zero,
+ which was fixed recently.
+
+
+- SSL mode to talk to APRS-IS. (Aprsc speaks SSL very efficiently.)
+
+- SCTP communication protocol to APRS-IS matching Aprsc's code
+
+- Embedded Perl and/or Python? That would allow folks to cook up
+ arbitrary action scripts for who knows what purposes...
+
+- Socket API to send/receive/monitor packets?
+ To get Protocol Buffer or JSON/BSON wrapped datasets of parsed frame
+ including lat/lon of the packet, type info, etc. ?
+ - BPQ32 protocol?
+ - AWGPE protocol?
+ - Copy-of-APRSIS ? (okay(ish) for monitor + transmit to APRS-IS..)
+
+- Message destined to $MYCALL to be logged at at syslog INFO level,
+ and acknowledged(?)
+
+- Tx-IGate filter rules (interface.c)
+ Tx-IGate is lacking following things, but it is already doing most
+ important ones ('recipient heard recently in my service area')
+ - figure out (at parse_aprs() ?) the number of hops a packet has
+ traversed so far, and log that on historydb (for recipient "locality"
+ analysis)
+ - test that message recipients known coordinates are within
+ "my service area"
+ --> separate filter from adjunct-style areas or distances?
+ - for position-less packets, send at first a position packet for
+ same source callsign, if available
+
+
+- Digipeater behaviour on "requested wideness exceeds allowed
+ max limit" and this is a directly heard packet:
+ - now mark all H-bits set
+ - ADDITION: inject transmitter callsign into first VIA slot.
+
+
+- Digipeating details of non-APRS UI protocols? Some way to handle
+ "WIDEn-N" on selected PIDs?
+- OpenTRAC support ? (UI, PID=0x77)
+ - Yes, on DIGIPEATER ONLY - what about WIDEn-N ? Y/N ?
+
+- Windows port?
+
+- Colorize debug printout (or make another tool to colorize aprx-rf.log)
+ - rx-packets green (or blue), tx-packets red
+ - port names/callsigns bolded, same with packet source callsigns
+
+- Use internal APRS packet parser to validate beacon configurations.
+ This should be able to flag bad input on RAW beacon data.
+
+- Calculate and note APRS DXes (like digi_ned does) ?
+- Calculate ALOHA range, and publish it?
+
+- Embedded SNTP client?
+ (Reads NTP time from 127.0.0.1:123 - or configured external source)
+
+ Supports Aprx-Aprx clustered protocol where all messages are equipped
+ with NTP timestamp and if in receiver's opinnion the time is too far off,
+ the network delays have been excessive and message is discarded.
+
+ This SNTP client _does_ _not_ adjust host system clock, only tracks
+ difference of it against a high(er)-quality NTP service elsewere.
+ The difference and drift parameters are used to convert current system
+ gettimeofday() result to NTP timestamp within the code, and to determine
+ how old something is, a NTP timestamp is compared against "current time."
+
+ If Aprx System has no time at all, one SNTP probe is made to determine
+ "initial system time".
+
+ This SNTP client will have two time management states:
+ - "current time"
+ - "new time"
+ If SNTP probes return times that agree with "current time",
+ the "new time" accumulator is discarded, and possible minor
+ drift sync is done on "current time."
+
+ If SNTP probes return times that do not agree with "current time", then
+ a "new time" accumulator is used to track the time. If the "new time" is
+ consistently tracked for 5 samples, "new time" becomes "current time".
+
+
+BoB:
+ Shucks, it would sure be nice if EVERY DIGIPEATER also had a
+ little smarts and could also report on its own RSSI.
+
+ That would give some people a clue what the digipeater is
+ hearing on its input. In its 10 minute beacon it could report:
+
+ "176RX, 109TX, 243 busy"
+
+ ON a ten minute basis (total 600 1 sec slots) this tells us that
+ 176 packets were decoded at this site, 109 were elegible for
+ further digipeating, and another 243 seconds the channel was
+ busy with other collisions and non decodable traffic. Leaving
+ 72 secnds the channel was clear.
+
+
+by Matti Aarnio - OH2MQK - oh2mqk-at-sral-fi - 2007-2014
diff --git a/VER b/VER
new file mode 100644
index 0000000..135b797
--- /dev/null
+++ b/VER
@@ -0,0 +1 @@
+542
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..35e5e7b
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+aprx-2.08.svn593
diff --git a/ViscousDigipeater.README b/ViscousDigipeater.README
new file mode 100644
index 0000000..bd04668
--- /dev/null
+++ b/ViscousDigipeater.README
@@ -0,0 +1,60 @@
+
+ Viscous Digipeater in APRX
+
+The Viscous Digipeater is variation of AX.25 Digipeater, which puts
+arriving packets into a time delay probation pipe (up to 9 seconds),
+before digipeating them.
+
+One can use a Viscous Digipeater as a fill-in system, which does not
+repeat on band a packet that it hears having already been repeated.
+It does have also benefits on Tx-IGate, which will not re-transmit
+to band something that already was heard there within the small window
+of a few seconds that the probation pipe takes.
+
+
+
+
+
+The trick is that duplicate accounting is done right up front at
+arrival packet arrival time, and depending on what kind of source
+a similar packet arrives from (or none at all), that delayed packet
+may get bumped off the delay pipe, or even be sent out.
+
+
+The rules are a bit more complicated than for plain simple digipeater,
+but not impossibly so:
+
+ Packets arriving from non-viscous sources trump those waiting in
+ viscous queues. First one arriving will be transmitted, unless
+ the viscous queue has no longer this packet (but it was there.)
+ ( delayed_seen > 0, seen == 1, pbuf == NULL -> drop this )
+ ( delayed_seen > 0, seen == 1, pbuf != NULL -> clean pbuf, transmit this )
+ ( delayed_seen == 0, seen == 1 -> transmit this )
+
+ Subsequent packets arriving from non-viscous sources are dropped
+ as duplicates ( seen > 1 -> drop this )
+
+ Packets arriving from any viscous source are dropped, if there
+ already was some direct delivery packet
+ ( delayed_seen > 0, seen > 0 -> drop )
+
+ First packet arriving from any viscous source is put on viscous queue,
+ unless there was non-viscous packet previously.
+ ( delayed_seen == 1, seen == 0 -> put this on viscous queue )
+
+ Then among viscous sources:
+ - "Transmitter" kind source: an <interface> which is same as
+ that of <digipeater>'s transmitter <interface>.
+ - "Elsewhere" kind source: an <interface> which is some other
+ than that of transmitter's, but has viscous-delay > 0
+
+ Account the number of viscous sourced packets sourced from "transmitter"
+ if (source_is_transmitter)
+ seen_on_transmitter += 1;
+
+ For second and subsequent viscous sourced packets, if any of observed
+ packets came from transmitter (seen_on_transmitter > 0), then drop
+ current packet, and clear possible viscous queued pbuf.
+
+
+Matti Aarnio, OH2MQK, 2009-10-21
diff --git a/ViscousDigipeaterTxEffect.png b/ViscousDigipeaterTxEffect.png
new file mode 100644
index 0000000..ea0ae3e
Binary files /dev/null and b/ViscousDigipeaterTxEffect.png differ
diff --git a/agwpesocket.c b/agwpesocket.c
new file mode 100644
index 0000000..86955f3
--- /dev/null
+++ b/agwpesocket.c
@@ -0,0 +1,731 @@
+/* **************************************************************** *
+ * *
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ * **************************************************************** */
+#include "aprx.h"
+#ifdef ENABLE_AGWPE
+
+/*** AGWPE interface description from Xastir + AGWPE documents.
+ *** As those documents are unclear, I am using Xastir to supply
+ *** clue as to what really needs to be done.
+
+
+// How to Start your application and register with AGW Packet Engine
+
+// First of all create a stream socket and connect with
+// AGW Packet Engine ip address at port 8000.
+// If your application is running at the same machine with
+// AGWPE then as ip address use localhost (127.0.0.1).
+//
+// After connecting you need to register your application's
+// callsign if your application need to establish AX.25 connections.
+// If your application just do monitoring and sends Unproto frames,
+// no need for callsign registration.
+// You can register as many as 100 different callsigns.
+// If you register a callsign then AGW Packet Engine accepts AX.25
+// connections for this call.
+// You then ask AGWPE to send you the radioport information.
+// How many radioports are with their description.
+// After that you can create your windows or anything else.
+//
+// Now you are ready.
+// If you wish to do monitoring then you must enable monitoring.
+
+
+// Transmit a special frame in Raw AX25 format
+//
+// The frame must be in RAW AX25 format as should be txed by
+// the modem (no need for bit stuffing etc)
+// You can use this function to tx for instance an unproto
+// frame with a special PID or any other frame different from normal AX25 Format.
+//
+// Port field is the port where we want the data to tx
+// DataKind field = MAKELONG('K',0); The ASCII value of letter K
+// CallFrom empty (NULL)
+// CallTo empty (NULL)
+// DataLen is the length of the data that follow
+// USER is Undefined
+//
+// the whole frame with the header is
+//
+// [ HEADER ]
+// [port][DataKind][CallFrom][CallTo ][DataLen][USER][Data ]
+// 4bytes 4bytes 10bytes 10bytes 4bytes 4bytes DataLen Bytes
+//
+
+
+// ASK RadioPorts Number and descriptions
+//
+// Port field must be 0
+// DataKind field =MAKELONG('G',0); The ASCII value of letter G
+// CallFrom is empty (NULL)
+// CallTo is empty(NULL)
+// DataLen must be 0
+// USER is undefined
+// No data field must exists
+// [ HEADER ]
+// [port][DataKind][CallFrom][CallTo ][DataLen][USER]
+// 4bytes 4bytes 10bytes 10bytes 4bytes 4bytes
+//
+
+// ASK To start receiving AX25 RAW Frames
+//
+// Sending again thos command will disable this service.
+// You can start and stop this service any times you needed
+//
+// Port field no needed set it to 0
+// DataKind field =MAKELONG('k',0); The ASCII value of lower case letter k
+// CallFrom is empty no needed
+// CallTo is empty no needed
+// DataLen must be 0
+// USER is undefined
+// No data field must be present
+//
+// the whole frame with the header is
+//
+// [ HEADER ]
+// [port][DataKind][CallFrom][CallTo ][DataLen][USER]
+// 4bytes 4bytes 10bytes 10bytes 4bytes 4bytes
+
+
+// Raw AX25 Frames
+//
+// You can receive RAW AX25 frames if you enable this service.
+// Those frames are all the packet valid frames received from
+// any radioport. The frame is exactly the same as the pure
+// AX25 frame with the follow additions.
+//
+// The first byte always contains the radioport number 0 for 1st radioport.
+// There is no FCS field at the end of the frame.
+// There is no bit stuffing.
+// The LOWORD Port field is the port which heard the frame
+// The LOWORD DataKind field ='K'; The ASCII value of letter K
+// CallFrom the callsign of the station who TX the frame(Source station)
+// CallTo The callsign of the destination station
+// DataLen is the length of the DATA field(the length of the frame
+// USER is undefined.
+// the whole frame with the header is
+// [port][DataKind][CallFrom][CallTo ][DataLen][USER][DATA ]
+// 4bytes 4bytes 10bytes 10bytes 4bytes 4bytes DataLen Bytes
+
+
+// 1.UNPROTO monitor frame
+//
+// The LOWORD Port field is the port which heard the frame
+// The LOWORD DataKind field ='U'; The ASCII value of letter U
+// CallFrom= is the call from the station we heard the Packet
+// CallTo =is the destination call (CQ,BEACON etc)
+// DataLen= is the length of the data that follow
+// the whole frame with the header is
+// [port][DataKind][CallFrom][CallTo ][DataLen][USER][Data ]
+// 4bytes 4bytes 10bytes 10bytes 4bytes 4bytes DataLen Bytes
+
+
+// 4.RadioPort information
+//
+// The LOWORD Port field is always 0
+// The LOWORD DataKind field ='G'; The ASCII value of letter G
+// CallFrom empty(NULL)
+// CallTo empty(NULL)
+// DataLen is the length of the data that follow
+// USER is undefined
+// the whole frame with the header is
+// [port][DataKind][CallFrom][CallTo ][DataLen][USER][Data ]
+// 4bytes 4bytes 10bytes 10bytes 4bytes 4bytes DataLen Bytes
+// the data field format is as follow in plain text
+// howmany ports ;1st radioport description;2nd radioport;....;last radioport describtion
+// like
+// 2;TNC2 on serialport 1;OE5DXL on serialport2;
+// We have here 2 radioports. The separator is the ';'
+
+// 10. Reply to a 'G' command. This frame returns the radioport number
+// and description
+//
+// Port field is always 0
+// LOWORD DataKind field ='G'. The ASCII value of letter G
+// CallFrom is empty (NULL)
+// CallTo is empty(NULL)
+// DataLen =The number of bytes of DATA field
+// USER is undefined
+// DATA field conatins the radioport description
+// [ HEADER ]
+// [port][DataKind][CallFrom][CallTo ][DataLen][USER] [DATA]
+// 4bytes 4bytes 10bytes 10bytes 4bytes 4bytes Datalen bytes.
+//
+// The DATA field is organised as follow and is in plain ASCII.
+//
+// Number of Radioports;First radioport description(Friendlyname);Second radioport description(Friendly name)...........
+//
+// Number of radioports=a Decimal Value. A value of 3 means 3 radioports
+// Radioport description= A string that describes the radioport.
+// The separator between fields is the letter ';'.
+// Just parse the whole DATA field and use as separator the ';'
+
+
+
+//
+// Xastir parses integer data like this: That is, it is LITTLE ENDIAN
+//
+// Fetch the length of the data portion of the packet
+// data_length = (unsigned char)(input_string[31]);
+// data_length = (data_length << 8) + (unsigned char)(input_string[30]);
+// data_length = (data_length << 8) + (unsigned char)(input_string[29]);
+// data_length = (data_length << 8) + (unsigned char)(input_string[28]);
+//
+
+***/
+
+// Socket communication packet header
+struct agwpeheader {
+ uint32_t radioPort; // 0..3
+ uint32_t dataKind; // 4..7
+ uint8_t fromCall[10]; // 8..17
+ uint8_t toCall[10]; // 18..27
+ uint32_t dataLength; // 28..31
+ uint32_t userField; // 32..35
+};
+
+
+struct agwpesocket; // forward declarator
+
+// One agwpecom per connection to AGWPE
+struct agwpecom {
+ int fd;
+ struct timeval wait_until;
+
+ const struct netresolver *netaddr;
+
+ int socketscount;
+ const struct agwpesocket **sockets;
+
+ int wrlen;
+ int wrcursor;
+
+ int rdneed; // this much in rdbuf before decision
+ int rdlen;
+ int rdcursor;
+
+ uint8_t wrbuf[4196];
+ uint8_t rdbuf[4196];
+};
+
+// One agwpesocket per interface
+struct agwpesocket {
+ int portnum;
+ const struct aprx_interface *iface;
+ struct agwpecom *com;
+};
+
+
+static struct agwpecom **pecom;
+static int pecomcount;
+
+
+static uint32_t get_le32(uint8_t *u) {
+ return (u[3] << 24 |
+ u[2] << 16 |
+ u[1] << 8 |
+ u[0]);
+}
+
+static void set_le32(uint8_t *u, uint32_t value) {
+ u[0] = (uint8_t)value;
+ value >>= 8;
+ u[1] = (uint8_t)value;
+ value >>= 8;
+ u[2] = (uint8_t)value;
+ value >>= 8;
+ u[3] = (uint8_t)value;
+}
+
+
+static struct agwpecom *agwpe_find_or_add_com(const char *hostname, const char *hostport)
+{
+ struct agwpecom *com;
+ int i;
+
+ for (i = 0; i < pecomcount; ++i) {
+ com = pecom[i];
+ if (strcasecmp(hostname,com->netaddr->hostname) == 0 &&
+ strcasecmp(hostport,com->netaddr->port) == 0) {
+ return com; // Found it!
+ }
+ }
+
+ // Did not find it, create it..
+
+ com = calloc(1, sizeof(*com));
+ com->fd = -1;
+ com->netaddr = netresolv_add(hostname, hostport);
+ com->rdneed = sizeof(struct agwpeheader);
+ tv_timeradd_millis(&com->wait_until, &tick, 30000); // redo in 30 seconds or so
+
+ ++pecomcount;
+ pecom = realloc(pecom, sizeof(void*)*pecomcount);
+ pecom[pecomcount-1] = com;
+
+ return com;
+}
+
+void *agwpe_addport(const char *hostname, const char *hostport, const char *agwpeport, const struct aprx_interface *interface)
+{
+ int agwpeportnum = atoi(agwpeport);
+ struct agwpesocket *S;
+ struct agwpecom *com;
+
+ if (agwpeportnum < 1 || agwpeportnum > 999) {
+ if (debug)
+ printf("ERROR: Bad AGWPE port number value, accepted range: 1 to 999\n");
+ return NULL;
+ }
+
+ S = calloc(1, sizeof(*S));
+
+ com = agwpe_find_or_add_com(hostname, hostport);
+
+ com->socketscount++;
+ com->sockets = realloc(com->sockets, sizeof(void*)*com->socketscount);
+ com->sockets[com->socketscount-1] = S;
+
+ S->iface = interface;
+ S->com = com;
+ S->portnum = agwpeportnum-1;
+
+ return S;
+}
+
+
+// close the AGWPE communication socket, retry its call at some point latter
+static void agwpe_reset(struct agwpecom *com, const char *why)
+{
+ com->wrlen = com->wrcursor = 0;
+ tv_timeradd_millis(&com->wait_until, &tick, 30000); // redo in 30 seconds or so
+
+ if (debug>1)
+ printf("Resetting AGWPE socket; %s\n", why);
+
+ if (com->fd < 0) {
+ // Should not happen..
+ return;
+ }
+
+ close(com->fd);
+ com->fd = -1;
+}
+
+
+/*
+ * agwpe_flush() -- write out buffered data - at least partially
+ */
+static void agwpe_flush(struct agwpecom *com)
+{
+ int i, len;
+
+ if (com->fd < 0) return; // nothing to do!
+
+ if ((com->wrlen == 0) || (com->wrlen > 0 && com->wrcursor >= com->wrlen)) {
+ com->wrlen = com->wrcursor = 0; /* already all written */
+ return;
+ }
+
+ /* Now there is some data in between wrcursor and wrlen */
+
+#ifndef MSG_NOSIGNAL
+# define MSG_NOSIGNAL 0 /* This exists only on Linux */
+#endif
+
+ len = com->wrlen - com->wrcursor;
+ if (len > 0) {
+ i = send(com->fd, com->wrbuf + com->wrcursor, len, MSG_NOSIGNAL);
+ /* No SIGPIPE if the
+ receiver is out,
+ or pipe is full
+ because it is doing
+ slow reconnection. */
+ } else
+ i = 0;
+ if (i < 0 && (errno == EPIPE ||
+ errno == ECONNRESET ||
+ errno == ECONNREFUSED ||
+ errno == ENOTCONN)) {
+ /* Sending failed, reset it.. */
+ agwpe_reset(com,"write to remote closed socket");
+ return;
+ }
+ if (i > 0) { /* wrote something */
+ com->wrcursor += i;
+ len = com->wrlen - com->wrcursor;
+ if (len == 0) {
+ com->wrcursor = com->wrlen = 0; /* wrote all ! */
+ } else {
+ /* compact the buffer a bit */
+ memcpy(com->wrbuf, com->wrbuf + com->wrcursor, len);
+ com->wrcursor = 0;
+ com->wrlen = len;
+ }
+ }
+}
+
+
+void agwpe_sendto(const void *_ap, const uint8_t *axaddr, const int axaddrlen, const char *axdata, const int axdatalen) {
+
+ struct agwpesocket *agwpe = (struct agwpesocket*)_ap;
+ struct agwpecom *com = agwpe->com;
+ int space = sizeof(com->wrbuf) - com->wrlen;
+ struct agwpeheader hdr;
+
+ if (debug) {
+ // printf("agwpe_sendto(->%s, axlen=%d)", S->ttycallsign[tncid], ax25rawlen);
+ }
+ if (com->fd < 0) {
+ if (debug)
+ printf("NOTE: Write to non-open AGWPE socket discarded.");
+ return;
+ }
+
+ agwpe_flush(com); // write out buffered data, if any
+
+ if (space < (sizeof(struct agwpeheader) + axaddrlen + axdatalen)) {
+ // Uh, no space at all!
+ if (debug)
+ printf("ERROR: No buffer space to send data to AGWPE socket");
+ return;
+ }
+
+ memset(&hdr, 0, sizeof(hdr));
+ set_le32((uint8_t*)(&hdr.radioPort), agwpe->portnum);
+ set_le32((uint8_t*)(&hdr.dataKind), 'K');
+ set_le32((uint8_t*)(&hdr.dataLength), axaddrlen + axdatalen);
+
+ memcpy(com->wrbuf + com->wrlen, &hdr, sizeof(hdr));
+ com->wrlen += sizeof(hdr);
+ memcpy(com->wrbuf + com->wrlen, axaddr, axaddrlen);
+ com->wrlen += axaddrlen;
+ memcpy(com->wrbuf + com->wrlen, axdata, axdatalen);
+ com->wrlen += axdatalen;
+
+ agwpe_flush(com); // write out buffered data
+
+ // Account transmission
+ erlang_add(agwpe->iface->callsign, ERLANG_TX, axaddrlen+axdatalen + 10, 1); // agwpe_sendto()
+}
+
+
+
+static int agwpe_controlwrite(struct agwpecom *com, const uint32_t oper) {
+
+ int space = sizeof(com->wrbuf) - com->wrlen;
+ struct agwpeheader hdr;
+
+ if (debug) {
+ printf("agwpe_controlwrite(oper=%x (%c))\n", oper, oper);
+ }
+ if (com->fd < 0) {
+ if (debug)
+ printf("NOTE: Write to non-open AGWPE socket discarded.\n");
+ return -1;
+ }
+
+ agwpe_flush(com); // write out buffered data, if any
+
+ if (space < sizeof(hdr)) {
+ // No room :-(
+ return -1;
+ }
+
+ memset(&hdr, 0, sizeof(hdr));
+ set_le32((uint8_t*)(&hdr.dataKind), oper);
+
+ if (debug)
+ hexdumpfp(stdout, (const uint8_t *)&hdr, sizeof(hdr), 0);
+
+
+ memcpy(com->wrbuf + com->wrlen, &hdr, sizeof(hdr));
+ com->wrlen += sizeof(hdr);
+
+ agwpe_flush(com); // write out buffered data
+ return 0;
+}
+
+
+static void agwpe_parse_raw_ax25(struct agwpecom *com,
+ struct agwpeheader *hdr, const uint8_t *rxbuf)
+{
+#warning "WRITEME: AGWPE Raw AX.25 reception"
+}
+
+
+static void agwpe_parsereceived(struct agwpecom *com,
+ struct agwpeheader *hdr, const uint8_t *rxbuf)
+{
+
+ uint8_t frameType = hdr->dataKind;
+
+ if (debug) {
+ int i;
+ int rcvlen = hdr->dataLength;
+
+ printf("AGWPE hdr radioPort=%d dataKind=0x%x fromcall='%s' tocall='%s'"
+ " datalen=%d userfield=%x\n",
+ hdr->radioPort, hdr->dataKind, hdr->fromCall, hdr->toCall,
+ rcvlen, hdr->userField);
+
+ if (rcvlen > 512) rcvlen=512;
+ printf("AGWPE Data: ");
+ for (i = 0; i < rcvlen; ++i)
+ printf(" %02x", rxbuf[i]);
+ printf("\n");
+ printf("AGWPE Text: ");
+ for (i = 0; i < rcvlen; ++i) {
+ uint8_t c = rxbuf[i];
+ if (32 <= c && c <= 126)
+ printf(" %c", c);
+ else
+ printf(" %02x", c);
+ }
+ printf("\n");
+ printf("AGWPE AX25: ");
+ for (i = 0; i < rcvlen; ++i) {
+ uint8_t c = rxbuf[i] >> 1;
+ if (32 <= c && c <= 126)
+ printf(" %c", c);
+ else
+ printf(" %02x", c);
+ }
+ printf("\n");
+ }
+
+
+ switch (frameType) {
+ case 'K': // Raw AX.25 frame received
+ agwpe_parse_raw_ax25(com, hdr, rxbuf);
+ break;
+
+ default: // Everything else: discard
+ break;
+ }
+}
+
+
+
+static void agwpe_read(struct agwpecom *com) {
+
+ int rcvspace = sizeof(com->rdbuf) - com->rdlen;
+ int rcvlen;
+ struct agwpeheader hdr;
+
+ if (com->fd < 0) {
+ // Should not happen..
+ return;
+ }
+
+ if (com->rdlen > com->rdcursor) {
+ memcpy(com->rdbuf, com->rdbuf + com->rdcursor,
+ com->rdlen - com->rdcursor);
+ com->rdlen -= com->rdcursor;
+ }
+ com->rdcursor = 0;
+
+
+ rcvlen = read(com->fd, com->rdbuf + com->rdlen, rcvspace);
+ if (rcvlen > 0)
+ com->rdlen += rcvlen;
+ if (com->rdlen < com->rdneed) {
+ // insufficient amount received, continue with it latter
+ return;
+ }
+
+ while (com->rdlen >= com->rdneed) {
+
+ hdr.radioPort = get_le32(com->rdbuf + 0);
+ hdr.dataKind = get_le32(com->rdbuf + 4);
+ memcpy(hdr.fromCall, com->rdbuf + 8, 10);
+ memcpy(hdr.toCall, com->rdbuf + 18, 10);
+ hdr.dataLength = get_le32(com->rdbuf + 28);
+ hdr.userField = get_le32(com->rdbuf + 32);
+
+ if (com->rdneed < (sizeof(hdr) + hdr.dataLength)) {
+ // recalculate needed data size
+ com->rdneed = sizeof(hdr) + hdr.dataLength;
+ }
+ if (com->rdneed > sizeof(com->rdbuf)) {
+ // line noise or something...
+ agwpe_reset(com,"received junk data");
+ return;
+ }
+ if (com->rdlen < com->rdneed) {
+ // insufficient amount received..
+ break;
+ }
+
+ // Process received frame
+ agwpe_parsereceived(com, &hdr, com->rdbuf + sizeof(hdr));
+
+ com->rdcursor += sizeof(hdr) + hdr.dataLength;
+ if (com->rdlen > com->rdcursor) {
+ memcpy(com->rdbuf, com->rdbuf + com->rdcursor,
+ com->rdlen - com->rdcursor);
+ com->rdlen -= com->rdcursor;
+ }
+ com->rdcursor = 0;
+ com->rdneed = sizeof(hdr);
+ }
+}
+
+
+static void agwpe_connect(struct agwpecom *com) {
+ int i;
+
+ // Initial protocol reading parameters
+ com->rdcursor = 0;
+ com->rdneed = sizeof(struct agwpeheader);
+
+ // Create socket
+ if (debug>1) {
+ printf("AGWPE socket(%d %d %d)\n",
+ com->netaddr->ai.ai_family, com->netaddr->ai.ai_socktype,
+ com->netaddr->ai.ai_protocol);
+ }
+ com->fd = socket(com->netaddr->ai.ai_family, com->netaddr->ai.ai_socktype,
+ com->netaddr->ai.ai_protocol);
+ if (com->fd < 0) {
+ if (debug)
+ printf("ERROR at AGWPE socket creation: errno=%d %s\n",errno,strerror(errno));
+ agwpe_reset(com,"error at socket() creation");
+ return;
+ }
+ // Put it on non-blocking mode
+ fd_nonblockingmode(com->fd);
+
+ // Connect
+ i = connect(com->fd, com->netaddr->ai.ai_addr, com->netaddr->ai.ai_addrlen);
+ // Should result "EINPROGRESS"
+ if (i < 0 && (errno != EINPROGRESS && errno != EINTR)) {
+ // Unexpected fault!
+ if (debug)
+ printf("ERROR on non-blocking connect(): errno=%d (%s)\n", errno, strerror(errno));
+ agwpe_reset(com,"connect failure");
+ return;
+ }
+
+ // Aprx will snoop everything that happens on radio ports,
+ // and receive frames in raw AX.25.
+
+ // Queue necessary configuration parameters on newly constructed socket
+
+ agwpe_controlwrite(com, 'k'); // Ask for raw AX.25 frames
+ agwpe_controlwrite(com, 'm'); // Ask for full monitoring of all interfaces
+}
+
+
+/*
+ * agwpe_init()
+ */
+
+void agwpe_init(void)
+{
+ /* nothing.. */
+}
+
+/*
+ * agwpe_start()
+ */
+
+void agwpe_start(void)
+{
+ /* nothing.. */
+}
+
+
+
+/*
+ * agwpe_prepoll() -- prepare system for next round of polling
+ */
+
+int agwpe_prepoll(struct aprxpolls *app)
+{
+ int idx = 0; /* returns number of *fds filled.. */
+ int i;
+ struct agwpecom *S;
+ struct pollfd *pfd;
+
+ for (i = 0; i < pecomcount; ++i) {
+ S = pecom[i];
+ if (S->fd < 0) {
+ /* Not an open TTY, but perhaps waiting ? */
+ if ((S->wait_until.tv_sec != 0) && tv_timercmp(&S->wait_until, &tick) > 0) {
+ if (tv_timerdelta_millis(&S->wait_until, &tick) > 60000) {
+ // Verify that wait is not too long -- system time jumped backwards? (but not 68 years..)
+ S->wait_until = tick;
+ }
+
+ /* .. waiting for future! */
+ if (tv_timercmp(&app->next_timeout, &S->wait_until) > 0)
+ app->next_timeout = S->wait_until;
+ /* .. but only until our timeout,
+ if it is sooner than global one. */
+ continue; /* Waiting on this one.. */
+ }
+
+ /* Waiting or not, FD is not open, and deadline is past.
+ Lets try to open! */
+
+ agwpe_connect(S);
+
+ }
+ /* .. No open FD */
+ /* Still no open FD ? */
+ if (S->fd < 0)
+ continue;
+
+ // FD is open, lets mark it for poll read..
+ pfd = aprxpolls_new(app);
+ pfd->fd = S->fd;
+ pfd->events = POLLIN | POLLPRI;
+ pfd->revents = 0;
+ // .. and if needed, poll write.
+ if (S->wrlen > S->wrcursor)
+ pfd->events |= POLLOUT;
+
+ ++idx;
+ }
+ return idx;
+}
+
+/*
+ * agwpe_postpoll() -- Done polling, what happened ?
+ */
+
+int agwpe_postpoll(struct aprxpolls *app)
+{
+ int idx, i;
+
+ struct agwpecom *S;
+ struct pollfd *P;
+ for (idx = 0, P = app->polls; idx < app->pollcount; ++idx, ++P) {
+
+ if (!(P->revents & (POLLIN | POLLPRI | POLLERR | POLLHUP)))
+ continue; /* No read event we are interested in... */
+
+ for (i = 0; i < pecomcount; ++i) {
+ S = pecom[i];
+ if (S->fd != P->fd)
+ continue; /* Not this one ? */
+ /* It is this one! */
+
+ if (P->revents & POLLOUT)
+ agwpe_flush(S);
+
+ agwpe_read(S);
+ }
+ }
+
+ return 0;
+}
+
+#endif
diff --git a/apparmor.aprx b/apparmor.aprx
new file mode 100644
index 0000000..00b878f
--- /dev/null
+++ b/apparmor.aprx
@@ -0,0 +1,17 @@
+#include <tunables/global>
+
+/sbin/aprx {
+ #include <abstractions/base>
+ #include <abstractions/nameservice>
+
+
+ capability setgid,
+ capability setuid,
+ capability sys_chroot,
+
+
+ /etc/aprx.conf r,
+ owner /var/run/aprx.pid rwk,
+ owner /var/run/aprx.state rwk,
+ owner /var/log/aprx/* rwk,
+}
diff --git a/aprsis.c b/aprsis.c
new file mode 100644
index 0000000..85d3706
--- /dev/null
+++ b/aprsis.c
@@ -0,0 +1,1279 @@
+/* **************************************************************** *
+ * *
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ * **************************************************************** */
+
+/* This code works only with single aprsis-server instance! */
+
+#include "aprx.h"
+
+#ifndef DISABLE_IGATE
+
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <signal.h>
+
+#ifdef HAVE_NETINET_SCTP_H
+#include <netinet/sctp.h>
+#endif
+
+#if defined(HAVE_PTHREAD_CREATE) && defined(ENABLE_PTHREAD)
+#include <pthread.h>
+pthread_t aprsis_thread;
+pthread_attr_t pthr_attrs;
+#endif
+
+/*
+ * $aprsserver = "rotate.aprs.net:14580";
+ *
+ * re-resolve the $aprsserver at each connection setup!
+ *
+ * The APRS-IS system connection runs as separate sub-process, once it starts.
+ * This way the main-loop is independent from uncertainties of DNS resolving
+ * delay times in this part of the code.
+ *
+ */
+
+enum aprsis_mode {
+ MODE_TCP,
+ MODE_SSL,
+ MODE_SCTP,
+ MODE_DTLS
+};
+
+static char default_passcode[] = "-1";
+
+struct aprsis_host {
+ char *server_name;
+ char *server_port;
+ char *login;
+ char *pass;
+ char *filterparam;
+ int heartbeat_monitor_timeout;
+ enum aprsis_mode mode;
+};
+
+struct aprsis {
+ int server_socket;
+ struct aprsis_host *H;
+ time_t next_reconnect;
+ time_t last_read;
+ int wrbuf_len;
+ int wrbuf_cur;
+ int rdbuf_len;
+ int rdbuf_cur;
+ int rdlin_len;
+
+ char wrbuf[16000];
+ char rdbuf[3000];
+ char rdline[500];
+};
+
+char * const aprsis_loginid;
+static struct aprsis *AprsIS;
+static struct aprsis_host **AISh;
+static int AIShcount;
+static int AIShindex;
+static int aprsis_up = -1; /* up & down talking socket(pair),
+ The aprsis talker (thread/child)
+ uses this socket. */
+static int aprsis_down = -1; /* down talking socket(pair),
+ The aprx main loop uses this socket */
+//static dupecheck_t *aprsis_rx_dupecheck;
+
+//int aprsis_dupecheck_storetime = 30;
+
+
+extern int log_aprsis;
+extern int die_now;
+
+void aprsis_init(void)
+{
+ aprsis_up = -1;
+ aprsis_down = -1;
+}
+
+//void enable_aprsis_rx_dupecheck(void) {
+// aprsis_rx_dupecheck = dupecheck_new(aprsis_dupecheck_storetime);
+//}
+#if !(defined(HAVE_PTHREAD_CREATE) && defined(ENABLE_PTHREAD))
+static void sig_handler(int sig)
+{
+ die_now = 1;
+ signal(sig, sig_handler);
+}
+#endif
+
+/*
+ *Close APRS-IS server_socket, clean state..
+ */
+// APRS-IS communicator
+static void aprsis_close(struct aprsis *A, const char *why)
+{
+ if (A->server_socket >= 0)
+ close(A->server_socket); /* close, and flush write buffers */
+
+ A->server_socket = -1;
+
+ A->wrbuf_len = A->wrbuf_cur = 0;
+ A->next_reconnect = tick.tv_sec + 10;
+ A->last_read = tick.tv_sec;
+
+ if (!A->H)
+ return; /* Not connected, nor defined.. */
+
+ aprxlog("CLOSE APRSIS %s:%s %s",
+ A->H->server_name, A->H->server_port,
+ why ? why : "");
+}
+
+
+/*
+ * aprsis_queue_() - internal routine - queue data to specific APRS-IS instance
+ */
+// APRS-IS communicator
+static int aprsis_queue_(struct aprsis *A, const char * const addr, const char qtype,
+ const char *gwcall, const char * const text, int textlen)
+{
+ int i;
+ char addrbuf[1000];
+ int addrlen, len;
+ char * p;
+
+ /* Queue for sending to APRS-IS only when the socket is operational */
+ if (A->server_socket < 0)
+ return 1;
+
+ /* Here the A->H->login is always set. */
+
+ /*
+ * Append stuff on the writebuf, if it fits.
+ * If it does not fit, something is broken already
+ * and we just drop it..
+ *
+ * Just to make sure that the write pointer is not left
+ * rewound when all has been done...
+ */
+
+ if (A->wrbuf_cur >= A->wrbuf_len && A->wrbuf_len > 0)
+ A->wrbuf_cur = A->wrbuf_len = 0;
+
+ addrlen = 0;
+ if (addr)
+ addrlen = sprintf(addrbuf, "%s,qA%c,%s:", addr, qtype,
+ (gwcall
+ && *gwcall) ? gwcall : A->H->login);
+ aprsis_login = A->H->login;
+
+ len = addrlen + textlen;
+
+
+ /* Does it fit in ? */
+
+ if ((sizeof(A->wrbuf) - 10) <= (A->wrbuf_len + len)) {
+ /* The string does not fit in, perhaps it needs compacting ? */
+ if (A->wrbuf_cur > 0) { /* Compacting is possible ! */
+ memcpy(A->wrbuf, A->wrbuf + A->wrbuf_cur,
+ A->wrbuf_len - A->wrbuf_cur);
+ A->wrbuf_len -= A->wrbuf_cur;
+ A->wrbuf_cur = 0;
+ }
+
+ /* Check again if it fits in.. */
+ if ((sizeof(A->wrbuf) - 10) <= (A->wrbuf_len + len)) {
+ /* NOT! Too bad, drop it.. */
+ return 2;
+ }
+ }
+
+
+ /* Place it on our send buffer */
+
+ if (addrlen > 0) {
+ memcpy(A->wrbuf + A->wrbuf_len, addrbuf, addrlen);
+ A->wrbuf_len += addrlen;
+ }
+
+ /* If there is CR or LF within the packet, terminate packet at it.. */
+ p = memchr(text, '\r', textlen);
+ if (p != NULL) {
+ textlen = p - text;
+ }
+ p = memchr(text, '\n', textlen);
+ if (p != NULL) {
+ textlen = p - text;
+ }
+
+ /* Append CR+LF at the end of the packet */
+ p = (char*)(text + textlen);
+ *p++ = '\r';
+ *p++ = '\n';
+ textlen += 2;
+
+ memcpy(A->wrbuf + A->wrbuf_len, text, textlen);
+ A->wrbuf_len += textlen; /* Always supplied with tail newline.. */
+
+ /* -- debug --
+ fwrite(A->wrbuf,A->wrbuf_len,1,stdout);
+ return 0;
+ */
+
+ /* Try writing it right away: */
+
+ i = write(A->server_socket, A->wrbuf + A->wrbuf_cur,
+ A->wrbuf_len - A->wrbuf_cur);
+ if (i > 0) {
+ // the buffer's last character is \n, don't write it
+ if (log_aprsis)
+ aprxlog(A->wrbuf + A->wrbuf_cur, (A->wrbuf_len - A->wrbuf_cur) -1,
+ "<< %s:%s << ", A->H->server_name, A->H->server_port);
+
+ A->wrbuf_cur += i;
+ if (A->wrbuf_cur >= A->wrbuf_len) { /* Wrote all ! */
+ A->wrbuf_cur = A->wrbuf_len = 0;
+ }
+ }
+
+ return 0;
+}
+
+
+/*
+ * THIS CONNECT ROUTINE WILL BLOCK (At DNS resolving)
+ *
+ * This is why APRSIS communication is run at either
+ * a fork()ed child, or separate pthread from main loop.
+ */
+
+// APRS-IS communicator
+static void aprsis_reconnect(struct aprsis *A)
+{
+ struct addrinfo req, *ai, *a, *ap[21];
+ int i, n;
+ char *s;
+ char aprsislogincmd[3000];
+ const char *errstr;
+ int errcode;
+
+ memset(aprsislogincmd, 0, sizeof(aprsislogincmd)); // please valgrind
+
+ aprsis_close(A, "reconnect");
+
+ if (!A->H) {
+ A->H = AISh[0];
+ } else {
+ ++AIShindex;
+ if (AIShindex >= AIShcount)
+ AIShindex = 0;
+ A->H = AISh[AIShindex];
+ }
+
+ if (!A->H->login) {
+ if (log_aprsis)
+ aprxlog("FAIL - APRSIS-LOGIN not defined, no APRSIS connection!");
+
+ return; /* Will try to reconnect in about 60 seconds.. */
+ }
+ aprsis_login = A->H->login;
+
+ memset(&req, 0, sizeof(req));
+ req.ai_socktype = SOCK_STREAM;
+ req.ai_protocol = IPPROTO_TCP;
+ req.ai_flags = 0;
+#if 1
+ req.ai_family = AF_UNSPEC; /* IPv4 and IPv6 are both OK */
+#else
+ req.ai_family = AF_INET; /* IPv4 only */
+#endif
+ ai = NULL;
+
+
+ i = getaddrinfo(A->H->server_name, A->H->server_port, &req, &ai);
+ errstr = "address resolution failure";
+ errcode = errno;
+
+ if (i != 0) {
+
+ fail_out:;
+ /* Discard stuff and redo latter.. */
+
+ if (ai)
+ freeaddrinfo(ai);
+
+ aprsis_close(A, "fail on connect");
+
+ aprxlog("FAIL - Connect to %s:%s failed: %s - errno=%d - %s",
+ A->H->server_name, A->H->server_port, errstr, errno, strerror(errcode));
+ return;
+ }
+
+ /* Count the addresses */
+ memset(ap, 0, sizeof(ap));
+ for (n = 0, a = ai; a; a = a->ai_next, ++n) {
+ if (n < 20)
+ ap[n] = a;
+ else
+ break;
+ }
+ ap[n] = NULL;
+
+ if (n > 1) { /* more than one ? choose one at random as the first address,
+ then go through the address list in new sequence. */
+ n = rand() % n;
+ if (n > 0) {
+ a = ap[n];
+ ap[n] = ap[0];
+ ap[0] = a;
+ }
+ }
+
+ for (n = 0; (a = ap[n]) && A->server_socket < 0; ++n) {
+
+ errstr = "socket formation failed";
+
+ A->server_socket =
+ socket(a->ai_family, a->ai_socktype,
+ a->ai_protocol);
+ errcode = errno;
+
+ if (A->server_socket < 0)
+ continue;
+
+ errstr = "connection failed";
+ i = connect(A->server_socket, a->ai_addr, a->ai_addrlen);
+ errcode = errno;
+
+ if (i < 0) {
+ /* If connection fails, try next possible address */
+ close(A->server_socket);
+ A->server_socket = -1;
+ continue;
+ }
+ }
+
+ if (A->server_socket < 0)
+ goto fail_out;
+
+ freeaddrinfo(ai);
+ ai = NULL;
+
+
+ timetick(); // unpredictable time since system did last poll..
+
+ if (time_reset) {
+ if (debug) printf("In time_reset mode, no touching yet!\n");
+ A->next_reconnect = tick.tv_sec + 10;
+ return;
+ }
+
+ aprxlog("CONNECT APRSIS %s:%s",
+ A->H->server_name, A->H->server_port);
+
+ /* From now the socket will be non-blocking for its entire lifetime.. */
+ fd_nonblockingmode(A->server_socket);
+
+ /* We do at first sync writing of login, and such.. */
+ s = aprsislogincmd;
+ s += sprintf(s, "user %s pass %s vers %s %s", A->H->login,
+ A->H->pass, swname, swversion);
+ if (A->H->filterparam)
+ s += sprintf(s, " filter %s", A->H->filterparam);
+
+ A->last_read = tick.tv_sec;
+
+ aprsis_queue_(A, NULL, qTYPE_LOCALGEN, "", aprsislogincmd, strlen(aprsislogincmd));
+
+ return; /* just a place-holder */
+}
+
+
+// APRS-IS communicator
+static int aprsis_sockreadline(struct aprsis *A)
+{
+ int i, c;
+
+ /* Reads multiple lines from buffer,
+ Last one is left into incomplete state */
+
+ for (i = A->rdbuf_cur; i < A->rdbuf_len; ++i) {
+ c = 0xFF & (A->rdbuf[i]);
+ if (c == '\r' || c == '\n') {
+ /* End of line, process.. */
+ if (A->rdlin_len > 0) {
+ A->rdline[A->rdlin_len] = 0;
+ /* */
+ A->last_read = tick.tv_sec; /* Time stamp me ! */
+
+ if (log_aprsis)
+ aprxlog(A->rdline, A->rdlin_len,
+ ">> %s:%s >> ", A->H->server_name, A->H->server_port);
+
+ /* Send the A->rdline content to main program */
+ c = send(aprsis_up, A->rdline, A->rdlin_len, 0);
+ /* This may fail with SIGPIPE.. */
+ if (c < 0 && (errno == EPIPE ||
+ errno == ECONNRESET ||
+ errno == ECONNREFUSED ||
+ errno == ENOTCONN)) {
+ die_now = 1; // upstream socket send failed
+ }
+ }
+ A->rdlin_len = 0;
+ continue;
+ }
+ if (A->rdlin_len < sizeof(A->rdline) - 2) {
+ A->rdline[A->rdlin_len++] = c;
+ }
+ }
+ A->rdbuf_cur = 0;
+ A->rdbuf_len = 0; /* we ignore line reading */
+ return 0; /* .. this is placeholder.. */
+}
+
+// APRS-IS communicator
+static int aprsis_sockread(struct aprsis *A)
+{
+ int i;
+
+ int rdspace = sizeof(A->rdbuf) - A->rdbuf_len;
+
+ if (A->rdbuf_cur > 0) {
+ /* Read-out cursor is not at block beginning,
+ is there unread data too ? */
+ if (A->rdbuf_cur > A->rdbuf_len) {
+ memcpy(A->rdbuf, A->rdbuf + A->rdbuf_cur,
+ A->rdbuf_len - A->rdbuf_cur);
+ A->rdbuf_len -= A->rdbuf_cur;
+ } else
+ A->rdbuf_len = 0; /* all processed, mark its size zero */
+ A->rdbuf_cur = 0;
+
+ /* recalculate */
+ rdspace = sizeof(A->rdbuf) - A->rdbuf_len;
+ }
+
+ i = read(A->server_socket, A->rdbuf + A->rdbuf_len, rdspace);
+
+ if (i > 0) {
+
+ A->rdbuf_len += i;
+
+ /* we just ignore the readback.. but do time-stamp the event */
+ A->last_read = tick.tv_sec;
+
+ aprsis_sockreadline(A);
+ }
+
+ return i;
+}
+
+struct aprsis_tx_msg_head {
+ time_t then;
+ int addrlen;
+ int gwlen;
+ int textlen;
+ char qtype;
+};
+
+/*
+ * Read frame from a socket in between main-program and
+ * APRS-IS interface subprogram. (At APRS-IS side.)
+ *
+ */
+// APRS-IS communicator
+static void aprsis_readup(void)
+{
+ int i;
+ char buf[10000];
+ const char *addr;
+ const char *gwcall;
+ const char *text;
+ int textlen;
+ struct aprsis_tx_msg_head head;
+
+ i = recv(aprsis_up, buf, sizeof(buf), 0);
+ if (i == 0) { /* EOF ! */
+ if (debug>1) printf("Upstream fd read resulted eof status.\n");
+ die_now = 1;
+ return;
+ }
+ if (i < 0) {
+ return; /* Whatever was the reason.. */
+ }
+ buf[i] = 0; /* String Termination NUL byte */
+
+ memcpy(&head, buf, sizeof(head));
+ if (head.then + 10 < tick.tv_sec)
+ return; /* Too old, discard */
+ addr = buf + sizeof(head);
+
+ gwcall = addr + head.addrlen + 1;
+
+ text = gwcall + head.gwlen + 1;
+
+ textlen = head.textlen;
+ if (textlen <= 2)
+ return; // BAD!
+ if ((text + textlen) > (buf + i)) {
+ return; // BAD!
+ }
+
+ /*
+ printf("addrlen=%d addr=%s\n",head.addrlen, addr);
+ printf("gwlen=%d gwcall=%s\n",head.gwlen,gwcall);
+ printf("textlen=%d text=%s",head.textlen, text);
+ return;
+ */
+
+ /* Now queue the thing! */
+
+ if (AprsIS != NULL)
+ aprsis_queue_(AprsIS, addr, head.qtype, gwcall, text, textlen);
+}
+
+
+// main program side
+int aprsis_queue(const char *addr, int addrlen, const char qtype, const char *gwcall, const char *text, int textlen)
+{
+ static char *buf; /* Dynamically allocated buffer... */
+ static int buflen;
+ int i, len, gwlen = strlen(gwcall);
+ char *p;
+ struct aprsis_tx_msg_head head;
+ int newlen;
+// dupe_record_t *dp;
+
+ if (aprsis_down < 0) return -1; // No socket!
+
+ if (addrlen == 0) /* should never be... */
+ addrlen = strlen(addr);
+
+// if (aprsis_rx_dupecheck != NULL) {
+// dp = dupecheck_aprs( aprsis_rx_dupecheck,
+// addr, addrlen,
+// text, textlen );
+// if (dp != NULL) return 1; // Bad either as dupe, or due to alloc failure
+// }
+
+ newlen = sizeof(head) + addrlen + gwlen + textlen + 6;
+ if (newlen > buflen) {
+ buflen = newlen;
+ buf = realloc(buf, buflen);
+ memset(buf, 0, buflen); // (re)init it to silence valgrind
+ }
+
+ memset(&head, 0, sizeof(head));
+ head.then = tick.tv_sec;
+ head.addrlen = addrlen;
+ head.gwlen = gwlen;
+ head.textlen = textlen;
+ head.qtype = qtype;
+
+ memcpy(buf, &head, sizeof(head));
+ p = buf + sizeof(head);
+
+ memcpy(p, addr, addrlen);
+ p += addrlen;
+ *p++ = 0; /* string terminating 0 byte */
+ memcpy(p, gwcall, gwlen);
+ p += gwlen;
+ *p++ = 0; /* string terminating 0 byte */
+ memcpy(p, text, textlen);
+ p += textlen;
+ len = p - buf;
+ *p++ = 0;
+
+#ifndef MSG_NOSIGNAL
+# define MSG_NOSIGNAL 0 /* This exists only on Linux */
+#endif
+ i = send(aprsis_down, buf, len, MSG_NOSIGNAL); /* No SIGPIPE if the
+ receiver is out,
+ or pipe is full
+ because it is doing
+ slow reconnection. */
+
+ return (i != len);
+ /* Return 0 if ANY of the queue operations was successfull
+ Return 1 if there was some error.. */
+}
+
+
+// APRS-IS communicator
+static int aprsis_prepoll_(struct aprxpolls *app)
+{
+ struct pollfd *pfd;
+ struct aprsis *A = AprsIS;
+
+ if (A->last_read == 0)
+ A->last_read = tick.tv_sec; /* mark it non-zero.. */
+
+ if (A->server_socket < 0)
+ return -1; /* Not open, do nothing */
+
+ if (debug>3) printf("aprsis_prepoll_()\n");
+
+ if (time_reset) {
+ aprsis_close(A, "time_reset!");
+ }
+
+
+ /* Not all aprs-is systems send "heartbeat", but when they do.. */
+ if ((A->H->heartbeat_monitor_timeout > 0) &&
+ ((A->last_read + A->H->heartbeat_monitor_timeout - tick.tv_sec) < 0)) {
+
+ /*
+ * More than 120 seconds (2 minutes) since last time
+ * that APRS-IS systems told us something on the connection.
+ * There is a heart-beat ticking every 20 or so seconds.
+ */
+
+ aprsis_close(A, "heartbeat timeout");
+ }
+
+ /* FD is open, lets mark it for poll read.. */
+
+ pfd = aprxpolls_new(app);
+
+ pfd->fd = A->server_socket;
+ pfd->events = POLLIN | POLLPRI | POLLERR | POLLHUP;
+ pfd->revents = 0;
+
+ /* Do we have something for writing ? */
+ if (A->wrbuf_len) {
+ pfd->events |= POLLOUT;
+ }
+
+ return 0;
+}
+
+// APRS-IS communicator
+static int aprsis_postpoll_(struct aprxpolls *app)
+{
+ int i;
+ struct pollfd *pfd = app->polls;
+ struct aprsis *A = AprsIS;
+
+ if (debug>3) printf("aprsis_postpoll_() cnt=%d\n", app->pollcount);
+
+ for (i = 0; i < app->pollcount; ++i, ++pfd) {
+ if (pfd->fd == A->server_socket && pfd->fd >= 0) {
+ /* This is APRS-IS socket, and we may have some results.. */
+
+ if (pfd->revents & (POLLERR)) { /* Errors ? */
+ aprsis_close(A,"postpoll_ POLLERR");
+ continue;
+ }
+ if (pfd->revents & (POLLHUP)) { /* Errors ? */
+ aprsis_close(A,"postpoll_ POLLHUP");
+ continue;
+ }
+
+ if (pfd->revents & (POLLIN | POLLPRI)) { /* Ready for reading */
+ for (;;) {
+ i = aprsis_sockread(A);
+ if (i == 0) { /* EOF ! */
+ aprsis_close(A,"postpoll_ EOF");
+ continue;
+ }
+ if (i < 0)
+ break;
+ }
+ }
+
+ if (pfd->revents & POLLOUT) { /* Ready for writing */
+ /* Normal queue write processing */
+
+ if (A->wrbuf_len > 0 &&
+ A->wrbuf_cur < A->wrbuf_len) {
+ i = write(A->server_socket,
+ A->wrbuf +
+ A->wrbuf_cur,
+ A->wrbuf_len -
+ A->wrbuf_cur);
+ if (debug>2)
+ printf("%ld << %s:%s << write() rc= %d\n",
+ tick.tv_sec, A->H->server_name, A->H->server_port, i);
+
+ if (i < 0)
+ continue; /* Argh.. nothing */
+ // if (i == 0); /* What ? */
+
+ if (log_aprsis)
+ aprxlog(A->wrbuf + A->wrbuf_cur,
+ (A->wrbuf_len - A->wrbuf_cur) -1,
+ "<< %s:%s << ", A->H->server_name,
+ A->H->server_port);
+
+ A->wrbuf_cur += i;
+ if (A->wrbuf_cur >= A->wrbuf_len) { /* Wrote all! */
+ A->wrbuf_len = A->wrbuf_cur = 0;
+ } else {
+ /* partial write .. do nothing.. */
+ }
+ }
+ /* .. normal queue */
+ } /* .. POLLOUT */
+ } /* .. if fd == server_socket */
+ } /* .. for .. nfds .. */
+ return 1; /* there was something we did, maybe.. */
+}
+
+
+// APRS-IS communicator
+static void aprsis_cond_reconnect(void)
+{
+ if (AprsIS && /* First time around it may trip.. */
+ AprsIS->server_socket < 0 && (AprsIS->next_reconnect - tick.tv_sec) <= 0) {
+ aprsis_reconnect(AprsIS);
+ }
+}
+
+
+/*
+ * Main-loop of subprogram handling communication with
+ * APRS-IS network servers.
+ *
+ * This starts only when we have at least one <aprsis> defined without errors.
+ */
+// APRS-IS communicator
+static void aprsis_main(void)
+{
+#if !(defined(HAVE_PTHREAD_CREATE) && defined(ENABLE_PTHREAD))
+ int ppid = getppid();
+#endif
+ struct aprxpolls app = APRXPOLLS_INIT;
+
+
+#if !(defined(HAVE_PTHREAD_CREATE) && defined(ENABLE_PTHREAD))
+ signal(SIGHUP, sig_handler);
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+ /* The main loop */
+ while (!die_now) {
+ struct pollfd *pfd;
+ int i;
+
+ timetick();
+
+ aprsis_cond_reconnect(); // may take unpredictable time..
+
+ timetick();
+
+#if !(defined(HAVE_PTHREAD_CREATE) && defined(ENABLE_PTHREAD))
+ // Parent-pid makes no sense in threaded setup
+ i = getppid();
+ if (i != ppid)
+ break; /* die now, my parent is gone.. */
+ if (i == 1)
+ break; /* a safety fallback case.. */
+#endif
+
+ aprxpolls_reset(&app);
+ tv_timeradd_seconds( &app.next_timeout, &tick, 5 );
+
+ if (aprsis_up >= 0) {
+ pfd = aprxpolls_new(&app);
+
+ pfd->fd = aprsis_up;
+ pfd->events = POLLIN | POLLPRI | POLLERR | POLLHUP;
+ pfd->revents = 0;
+ }
+
+ i = aprsis_prepoll_(&app);
+
+ // Prepolls are done
+ time_reset = 0;
+
+ if (tv_timercmp(&app.next_timeout, &tick) <= 0) {
+ tv_timeradd_seconds( &app.next_timeout, &tick, 1 ); // Just to be on safe side..
+ }
+
+ i = poll(app.polls, app.pollcount, aprxpolls_millis(&app));
+
+ timetick();
+
+ assert(app.polls != NULL);
+ if (app.polls[0].
+ revents & (POLLIN | POLLPRI | POLLERR | POLLHUP)) {
+ /* messaging channel has something for us, if
+ the channel reports EOF, we exit there and then. */
+ aprsis_readup();
+ }
+ i = aprsis_postpoll_(&app);
+ }
+ aprxpolls_free(&app); // valgrind..
+ /* Got "DIE NOW" signal... */
+ // exit(0);
+}
+
+
+/*
+ * aprsis_add_server() - old style configuration
+ */
+
+int aprsis_add_server(const char *server, const char *port)
+{
+ struct aprsis_host *H;
+
+ if (AprsIS == NULL) {
+ AprsIS = calloc(1,sizeof(*AprsIS));
+ }
+
+ H = calloc(1,sizeof(*H));
+ AISh = realloc(AISh, sizeof(AISh[0]) * (AIShcount + 1));
+ AISh[AIShcount] = H;
+
+ ++AIShcount;
+ /* No inc on AprsIScount */
+
+
+ H->server_name = strdup(server);
+ H->server_port = strdup(port);
+ H->heartbeat_monitor_timeout = 120; // Default timeout 120 seconds
+ H->login = strdup(aprsis_login); // global aprsis_login
+ H->pass = default_passcode;
+ if (H->login == NULL) H->login = strdup(mycall);
+
+ AprsIS->server_socket = -1;
+ AprsIS->next_reconnect = tick.tv_sec +10; /* perhaps somewhen latter.. */
+
+ return 0;
+}
+
+// old style configuration
+int aprsis_set_heartbeat_timeout(const int tout)
+{
+ int i = AIShcount;
+ struct aprsis_host *H;
+
+ if (i > 0)
+ --i;
+ H = AISh[i];
+
+ H->heartbeat_monitor_timeout = tout;
+
+ return 0;
+}
+
+// old style configuration
+int aprsis_set_filter(const char *filter)
+{
+ int i = AIShcount;
+ struct aprsis_host *H;
+
+ if (i > 0)
+ --i;
+ H = AISh[i];
+
+ H->filterparam = strdup(filter);
+
+ return 0;
+}
+
+// old style configuration
+int aprsis_set_login(const char *login)
+{
+ int i = AIShcount;
+ struct aprsis_host *H;
+
+ if (i > 0)
+ --i;
+ H = AISh[i];
+
+ H->login = strdup(login);
+
+ return 0;
+}
+
+#if defined(HAVE_PTHREAD_CREATE) && defined(ENABLE_PTHREAD)
+static void aprsis_runthread(void)
+{
+ sigset_t sigs_to_block;
+
+ sigemptyset(&sigs_to_block);
+ sigaddset(&sigs_to_block, SIGALRM);
+ sigaddset(&sigs_to_block, SIGINT);
+ sigaddset(&sigs_to_block, SIGTERM);
+ sigaddset(&sigs_to_block, SIGQUIT);
+ sigaddset(&sigs_to_block, SIGHUP);
+ sigaddset(&sigs_to_block, SIGURG);
+ sigaddset(&sigs_to_block, SIGPIPE);
+ sigaddset(&sigs_to_block, SIGUSR1);
+ pthread_sigmask(SIG_BLOCK, &sigs_to_block, NULL);
+
+ // generally the cancelability is enabled
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+
+ if (debug) printf("aprsis_runthread()\n");
+
+ aprsis_main();
+}
+
+
+void aprsis_start(void)
+{
+ int i;
+ int pipes[2];
+
+ if (AISh == NULL || AprsIS == NULL) {
+ fprintf(stderr,"***** NO APRSIS SERVER CONNECTION DEFINED *****");
+ return;
+ }
+
+ i = socketpair(AF_UNIX, SOCK_DGRAM, PF_UNSPEC, pipes);
+ if (i != 0) {
+ return; /* FAIL ! */
+ }
+
+ fd_nonblockingmode(pipes[0]);
+ fd_nonblockingmode(pipes[1]);
+ aprsis_down = pipes[0];
+ aprsis_up = pipes[1];
+
+ if (debug)printf("aprsis_start() PTHREAD socketpair(up=%d,down=%d)\n", aprsis_up, aprsis_down);
+
+ pthread_attr_init(&pthr_attrs);
+ /* 64 kB stack is enough for this thread (I hope!)
+ default of 2 MB is way too much...*/
+ pthread_attr_setstacksize(&pthr_attrs, 64*1024);
+
+ i = pthread_create(&aprsis_thread, &pthr_attrs, (void*)aprsis_runthread, NULL);
+ if (i == 0) {
+ if (debug) printf("APRSIS pthread_create() OK!\n");
+ } else { // FAIL!
+ close(pipes[0]);
+ close(pipes[1]);
+ aprsis_down = -1;
+ aprsis_up = -1;
+ }
+}
+
+// Shutdown the aprsis thread
+void aprsis_stop(void)
+{
+ die_now = 1;
+ pthread_cancel(aprsis_thread);
+ pthread_join(aprsis_thread, NULL);
+}
+
+
+#else // No pthread(3p)
+void aprsis_start(void)
+{
+ int i;
+ int pipes[2];
+
+ if (AISh == NULL || AprsIS == NULL) {
+ fprintf(stderr,"***** NO APRSIS SERVER CONNECTION DEFINED *****");
+ return;
+ }
+
+
+ i = socketpair(AF_UNIX, SOCK_DGRAM, PF_UNSPEC, pipes);
+ if (i != 0) {
+ return; /* FAIL ! */
+ }
+
+ i = fork();
+ if (i < 0) {
+ close(pipes[0]);
+ close(pipes[1]);
+ return; /* FAIL ! */
+ }
+
+ if (i == 0) {
+ /* Child -- the APRSIS talker */
+ aprsis_up = pipes[1];
+ fd_nonblockingmode(pipes[1]);
+ close(pipes[0]);
+ aprsis_main();
+ exit(0);
+ }
+
+
+ /* Parent */
+ close(pipes[1]);
+ fd_nonblockingmode(pipes[0]);
+ aprsis_down = pipes[0];
+}
+
+
+void aprsis_stop(void)
+{
+}
+#endif
+
+
+/*
+ * main-program side pre-poll
+ */
+int aprsis_prepoll(struct aprxpolls *app)
+{
+ int idx = 0; /* returns number of *fds filled.. */
+
+ struct pollfd *pfd;
+
+ // if (debug>3) printf("aprsis_prepoll()\n");
+
+ pfd = aprxpolls_new(app);
+
+ pfd->fd = aprsis_down; /* APRS-IS communicator server Sub-process */
+ pfd->events = POLLIN | POLLPRI;
+ pfd->revents = 0;
+
+ /* We react only for reading, if write fails because the socket is
+ jammed, that is just too bad... */
+
+ ++idx;
+
+ return idx;
+
+}
+
+/*
+ * main-program side reading of aprsis_down
+ */
+static int aprsis_comssockread(int fd)
+{
+ int i;
+ char buf[10000];
+
+ i = recv(fd, buf, sizeof(buf), 0);
+ if (debug>3) printf("aprsis_comsockread(fd=%d) -> i = %d\n", fd, i);
+ if (i == 0)
+ return 0;
+
+ /* TODO: do something with the data ?
+ A receive-only iGate does nothing, but Rx/Tx would do... */
+
+ /* Send the frame to Tx-IGate function */
+ if (i > 0)
+ igate_from_aprsis(buf, i);
+
+ return 1;
+}
+
+
+/*
+ * main-program side post-poll
+ */
+int aprsis_postpoll(struct aprxpolls *app)
+{
+ int i;
+ struct pollfd *pfd = app->polls;
+
+
+ // if (debug>3) printf("aprsis_postpoll()\n");
+
+ for (i = 0; i < app->pollcount; ++i, ++pfd) {
+ if (pfd->fd == aprsis_down) {
+ /* This is APRS-IS communicator subprocess socket,
+ and we may have some results.. */
+
+ if (pfd->revents) { /* Ready for reading */
+ i = aprsis_comssockread(pfd->fd);
+ if (i == 0) { /* EOF ! */
+ printf("APRS-IS coms subprocess socket EOF from main program side!\n");
+
+ continue;
+ }
+ if (i < 0)
+ continue;
+ }
+ }
+ } /* .. for .. nfds .. */
+ return 1; /* there was something we did, maybe.. */
+}
+
+
+// main program side
+int aprsis_config(struct configfile *cf)
+{
+ char *name, *param1;
+ char *str = cf->buf;
+ int has_fault = 0;
+ int line0 = cf->linenum;
+
+ struct aprsis_host *AIH = calloc(1,sizeof(*AIH));
+ AIH->login = strdup(mycall);
+ AIH->pass = default_passcode;
+ AIH->heartbeat_monitor_timeout = 120;
+ AIH->mode = MODE_TCP; // default mode
+
+ while (readconfigline(cf) != NULL) {
+ if (configline_is_comment(cf))
+ continue; /* Comment line, or empty line */
+
+ // It can be severely indented...
+ str = config_SKIPSPACE(cf->buf);
+
+ name = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+ config_STRLOWER(name);
+
+ param1 = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ if (strcmp(name, "</aprsis>") == 0) {
+ // End of this interface definition block
+ break;
+ }
+
+ // APRSIS parameters
+
+ // login
+ // server
+ // filter
+ // heartbeat-timeout
+ // mode
+
+ if (strcmp(name, "login") == 0) {
+ if (strcasecmp("$mycall",param1) != 0) {
+ // If not "$mycall" ..
+ config_STRUPPER(param1);
+ if (!validate_callsign_input(param1,0)) {
+ // bad input...
+ }
+ if (debug)
+ printf("%s:%d: INFO: LOGIN = '%s' '%s'\n",
+ cf->name, cf->linenum, param1, str);
+ if (AIH->login) free(AIH->login);
+ AIH->login = strdup(param1);
+ }
+
+ } else if (strcmp(name, "passcode") == 0) {
+ if (debug)
+ printf("%s:%d: INFO: PASSCODE = '%s' '%s'\n",
+ cf->name, cf->linenum, param1, str);
+ AIH->pass = strdup(param1);
+
+ } else if (strcmp(name, "server") == 0) {
+
+ if (AIH->server_name) free(AIH->server_name);
+ AIH->server_name = strdup(param1);
+
+ param1 = str;
+ str = config_SKIPTEXT(str, NULL);
+ // coverity[returned_pointer]
+ str = config_SKIPSPACE(str);
+ if ('1' <= *param1 && *param1 <= '9') {
+ // fixme: more input analysis?
+ int port = atoi(param1);
+ if (port < 1 || port > 65535) {
+ printf("%s:%d INFO: SERVER = '%s' port='%s' is not supplying valid TCP port number, defaulting to '14580'\n",
+ cf->name, cf->linenum, AIH->server_name, param1);
+ param1 = "14580";
+ }
+ AIH->server_port = strdup(param1);
+ } else if (*param1 == 0) {
+ // Default silently!
+ AIH->server_port = strdup("14580");
+ } else {
+ AIH->server_port = strdup("14580");
+ printf("%s:%d INFO: SERVER = '%s' port='%s' is not supplying valid TCP port number, defaulting to '14580'\n",
+ cf->name, cf->linenum, AIH->server_name, param1);
+ }
+
+ if (debug)
+ printf("%s:%d: INFO: SERVER = '%s':'%s'\n",
+ cf->name, cf->linenum, AIH->server_name, AIH->server_port);
+
+ } else if (strcmp(name, "heartbeat-timeout") == 0) {
+ int i = 0;
+ if (config_parse_interval(param1, &i)) {
+ // FIXME: Report parameter failure ...
+ printf("%s:%d: ERROR: HEARTBEAT-TIMEOUT = '%s' - bad parameter'\n",
+ cf->name, cf->linenum, param1);
+ has_fault = 1;
+ }
+ if (i < 0) { /* param failure ? */
+ i = 0; /* no timeout */
+ printf("%s:%d: ERROR: HEARTBEAT-TIMEOUT = '%s' - bad parameter'\n",
+ cf->name, cf->linenum, param1);
+ has_fault = 1;
+ }
+ AIH->heartbeat_monitor_timeout = i;
+
+ if (debug)
+ printf("%s:%d: INFO: HEARTBEAT-TIMEOUT = '%d' '%s'\n",
+ cf->name, cf->linenum, i, str);
+
+ } else if (strcmp(name, "filter") == 0) {
+ int l1 = (AIH->filterparam != NULL) ? strlen(AIH->filterparam) : 0;
+ int l2 = strlen(param1);
+
+ AIH->filterparam = realloc( AIH->filterparam, l1 + l2 +2 );
+
+ if (l1 > 0) {
+ AIH->filterparam[l1] = ' ';
+ memcpy(&(AIH->filterparam[l1+1]), param1, l2+1);
+ } else {
+ memcpy(&(AIH->filterparam[0]), param1, l2+1);
+ }
+
+ if (debug)
+ printf("%s:%d: INFO: FILTER = '%s' --> '%s'\n",
+ cf->name, cf->linenum, param1, AIH->filterparam);
+
+ } else if (strcmp(name, "mode") == 0) {
+ if (strcmp(param1,"tcp") == 0) {
+ AIH->mode = MODE_TCP;
+ } else if (strcmp(param1,"ssl") == 0) {
+ AIH->mode = MODE_SSL;
+ } else if (strcmp(param1,"sctp") == 0) {
+ AIH->mode = MODE_SCTP;
+ } else if (strcmp(param1,"dtls") == 0) {
+ AIH->mode = MODE_DTLS;
+ } else {
+ printf("%s:%d: ERROR: Unknown mode keyword in <aprsis> block: '%s'\n",
+ cf->name, cf->linenum, param1);
+ has_fault = 1;
+ }
+
+ } else {
+ printf("%s:%d: ERROR: Unknown configuration keyword in <aprsis> block: '%s'\n",
+ cf->name, cf->linenum, name);
+ has_fault = 1;
+ }
+ }
+ if (AIH->server_name == NULL) {
+ printf("%s:%d ERROR: This <aprsis> block does not define server!\n",
+ cf->name, line0);
+ has_fault = 1;
+ }
+ if (has_fault) {
+ if (AIH->server_name != NULL) free(AIH->server_name);
+ if (AIH->server_port != NULL) free(AIH->server_port);
+ if (AIH->filterparam != NULL) free(AIH->filterparam);
+ if (AIH->login != NULL) free(AIH->login);
+ free(AIH);
+
+ } else {
+ if (AprsIS == NULL) {
+ AprsIS = calloc(1, sizeof(*AprsIS));
+ AprsIS->server_socket = -1;
+ AprsIS->next_reconnect = tick.tv_sec +10;
+ }
+ if (AIH->pass == default_passcode) {
+ printf("%s:%d WARNING: This <aprsis> block does not define passcode!\n",
+ cf->name, line0);
+ printf("%s:%d WARNING: Your beacons and RF received will not make it to APRS-IS.\n",
+ cf->name, line0);
+ }
+
+ AISh = realloc(AISh, sizeof(AISh[0]) * (AIShcount + 1));
+ AISh[AIShcount] = AIH;
+ }
+ return has_fault;
+}
+
+#endif
diff --git a/aprx-complex.conf.in b/aprx-complex.conf.in
new file mode 100644
index 0000000..fd0a4de
--- /dev/null
+++ b/aprx-complex.conf.in
@@ -0,0 +1,528 @@
+#
+# Sample configuration file for the APRX-2 -- an APRS iGate and Digipeater
+#
+# This configuration is structured with Apache HTTPD style tags
+# which then contain subsystem parameters.
+#
+# Define the parameters in following order:
+# 1) <aprsis> ** zero to many
+# 2) <logging> ** zero or one
+# 3) <interface> ** there can be multiple!
+# 4) <beacon> ** zero to many
+# 5) <telemetry> ** zero to many
+# 5) <digipeater> ** zero to many (at most one for each Tx)
+#
+
+
+#
+# Global macro for simplified callsign definition:
+# Usable for 99+% of cases.
+#
+
+mycall N0CALL-1
+
+#
+# Global macro for simplified "my location" definition in
+# place of explicit "lat nn lon mm" at beacons. Will also
+# give "my location" reference for "filter m/100".
+#
+#myloc lat ddmm.mmN lon dddmm.mmE
+
+
+# Define possibly multiple APRSIS servers, they are connected to
+# in Round-Robin fashion. There also exist DNS RR servers for
+# this use, one of them is "rotate.aprsis.net".
+<aprsis>
+# The aprsis-login parameter:
+# Station callsignSSID used for relaying APRS frames into APRS-IS.
+#
+#login $mycall # login defaults to $mycall macro
+
+#
+# Passcode for your callsign
+#
+passcode -1
+
+# APRS-IS server name and optional portnumber.
+#
+# WARNING: Do not change from default port number [14580]
+# unless you are absolutely certain you want
+# something else, and you allow that something
+# else also affect your tx-igate behaviour!
+#
+server rotate.aprs2.net
+#server euro.aprs2.net
+#server asia.aprs2.net
+#server noam.aprs2.net
+#server soam.aprs2.net
+#server aunz.aprs2.net
+
+# Some APRS-IS servers tell every about 20 seconds to all contact
+# ports that they are there and alive. Others are just silent.
+# Default value 3*"heartbeat" + some --> 120 (seconds)
+#
+#heartbeat-timeout 0 # Disabler in case your server does not do heartbeat
+#heartbeat-timeout 1m # Interval of one minute (60 seconds)
+
+# APRS-IS server may support some filter commands.
+# See: http://www.aprs-is.net/javAPRSFilter.aspx
+#
+# You can define the filter as single long quoted string, or as
+# many short segments with explaining comments following them.
+#
+# Usability of these filters for a Tx-iGate is dubious, but
+# they exist in case you for example want to Tx-iGate packets
+# from some source callsigns in all cases even when they are
+# not in your local area.
+#
+#filter "possibly multiple filter specs in quotes"
+#
+#filter "m/100" # My-Range filter: positions within 100 km from my location
+#filter "f/OH2XYZ-3/50" # Friend-Range filter: 50 km of friend's last beacon position
+</aprsis>
+
+<logging>
+
+# pidfile is UNIX way to tell that others that this program is
+# running with given process-id number. This has compiled-in
+# default value of: pidfile @VARRUN@/aprx.pid
+#
+pidfile @VARRUN@/aprx.pid
+
+
+# rflog defines a rotatable file into which all RF-received packets
+# are logged. The host system can rotate it at any time without
+# need to signal the aprx that the file has been moved.
+#
+rflog @VARLOG@/aprx-rf.log
+
+# aprxlog defines a rotatable file into which most important
+# events on APRS-IS connection are logged, namely connects and
+# disconnects. The host system can rotate it at any time without
+# need to signal the aprx that the file has been moved.
+#
+aprxlog @VARLOG@/aprx.log
+
+# dprslog defines a rotatable file into which most important
+# events on DPRS receiver gateways are logged.
+# The host system can rotate it at any time without need to
+# signal the aprx that the file has been moved.
+#
+#dprslog @VARLOG@/dprs.log
+
+# erlangfile defines a mmap():able binary file, which stores
+# running sums of interfaces upon which the channel erlang
+# estimator runs, and collects data.
+# Depending on the system, it may be running on a filesystem
+# that actually retains data over reboots, or it may not.
+# With this backing store, the system does not loose cumulating
+# erlang data over the current period, if the restart is quick,
+# and does not stradle any exact minute.
+# (Do restarts at 15 seconds over an even minute..)
+# This file is around 0.7 MB per each interface talking APRS.
+# If this file is not defined or can not be created, internal
+# non-persistent in-memory storage will be used.
+#
+# Built-in default value is: @VARRUN@/aprx.state
+#
+#erlangfile @VARRUN@/aprx.state
+
+# erlang-loglevel is config file version of the "-l" option
+# pushing erlang data to syslog(3).
+# Valid values are (possibly) following: NONE, LOG_DAEMON,
+# LOG_FTP, LOG_LPR, LOG_MAIL, LOG_NEWS, LOG_USER, LOG_UUCP,
+# LOG_LOCAL0, LOG_LOCAL1, LOG_LOCAL2, LOG_LOCAL3, LOG_LOCAL4,
+# LOG_LOCAL5, LOG_LOCAL6, LOG_LOCAL7. If the parameter value is
+# not acceptable, list of accepted values are printed at startup.
+#
+#erlang-loglevel NONE
+
+# erlanglog defines a rotatable file into which erlang data
+# is written in text form.
+#
+#erlanglog @VARLOG@/erlang.log
+
+# erlang-log1min option logs to syslog/file also 1 minute
+# interval data from the program. (In addition to 10m and 60m.)
+#
+#erlang-log1min
+
+</logging>
+
+
+# *********** Multiple <interface> definitions follow *********
+
+# ax25-device Lists AX.25 ports by their callsigns that in Linux
+# systems receive APRS packets. If none are defined,
+# or the system is not Linux, the AX.25 network receiver
+# is not enabled. Used technologies need at least
+# Linux kernel 2.4.x
+#
+# tx-ok Boolean telling if this device is able to transmit.
+#
+#<interface>
+# ax25-device $mycall
+# tx-ok true # There be transmitter there!
+# #alias RELAY,WIDE,TRACE
+# #telem-to-is true # set to 'false' to disable
+#</interface>
+
+# The radio serial option. Parameters are:
+# - /dev/ttyUSB1 -- tty device
+# - 19200 -- baud rate, supported ones are:
+# 1200, 2400, 4800, 9600, 19200, 38400
+# - 8n1 -- 8-bits, no parity, one stop-bit,
+# no other supported modes
+# - KISS/XORSUM/SMACK -- KISS mode variants
+# TNC2 -- non-KISS text format variant
+# DPRS -- DPRS (RX) Gateway
+#
+#
+
+### KISS mode example with KISS TNC using TNCID 0
+#<interface>
+# serial-device /dev/ttyUSB0 19200 8n1 KISS
+# #alias RELAY,WIDE,TRACE
+# callsign N0CALL-14
+# #telem-to-is true # set to 'false' to disable
+#</interface>
+
+### KISS mode example with multiple sub-interfaces via TNCID multiplexing
+#<interface>
+# serial-device /dev/ttyUSB0 19200 8n1 KISS
+# #alias RELAY,WIDE,TRACE
+# ## kiss-subif 0 == KISS TNCID 0
+# <kiss-subif 0>
+# callsign N0CALL-14
+# tx-ok true
+# #telem-to-is true # set to 'false' to disable
+# </kiss-subif>
+# ## kiss-subif 3 == KISS TNCID 3
+# <kiss-subif 3>
+# callsign N0CALL-15
+# tx-ok false
+# #telem-to-is true # set to 'false' to disable
+# </kiss-subif>
+#</interface>
+
+#<interface>
+# serial-device /dev/ttyUSB1 19200 8n1 TNC2
+# callsign N0CALL-13
+# #telem-to-is true # set to 'false' to disable
+#</interface>
+
+#<interface>
+# serial-device /dev/ttyUSB1 19200 8n1 DPRS
+# callsign dprsgwcallsign # must define actual callsign
+# #tx-ok false # DPRS monitor can not do transmit
+# #telem-to-is true # set to 'false' to disable
+#</interface>
+
+#
+# "KISS" - plain basic KISS mode
+# "XORSUM" alias "BPQCRC" - KISS with BPQ "CRC" byte
+# "SMACK" alias "CRC16" - KISS with better CRC
+# "FLEXNET" - KISS with better CRC
+# "TNC2" - TNC2 monitor format
+# "DPRS" - DPRS (RX) GW
+#
+# Additional/alternate options for the serial-device
+#
+# "timeout 15m" sets a timeout monitor (an interval) to make
+# reopen/reconnect if the serial port/connection to radio
+# has failed somehow and nothing is heard. Local serial
+# ports do not in general need this. At APRS silent sites
+# this may cause repeated reconnects, but it should not
+# harm either. At busy sites this will handle reconnect
+# gracefully in case of network failures, and timeout
+# value can be shortened.
+#
+# "<kiss-subif 0>" sets optional multiplexer index on KISS type
+# connections. This id is specific for the multiplexer connection
+# on given port, and can be in range of 0 thru 7 for SMACK type
+# links, and up to 15 for KISS, and BPQ type links.
+# The kiss-subif is settable only for KISS-type connections.
+# The subif 0 is settable for TNC2 monitor format.
+#
+# "callsign NAME" sets callsign used in statistics displays,
+# and when the message is sent to APRS-IS.
+# If none are given, then it will use physical port name.
+# There can be multiple callsign parameters, if each are
+# prefixed with their own tncid setting.
+#
+# "tx-ok true" enables transmit. System will then also require
+# that used callsign is valid for AX.25.
+#
+# "initstring" is of two parts, the keyword, and then a string.
+# initstring "\xC0\xC0\xFF\xC0\r\nMO 0\rKISS $01\r"
+# The initstring is a binary string, "\x00" is encodable.
+# Of the usual C-style codes only "\r" and "\n" are understood.
+# The initstring is kiss-subif level option.
+#
+#</interface>
+
+
+# The tcp-device option defines a connection to remote socket
+# beyond which is a binary transparent connection to a serial
+# port. The parameter fields: literal IP address (IPv4 or IPv6),
+# then literal port number, and finally protocol mode.
+# KISS-protocol parameters are same as with normal serial port.
+#
+#<interface>
+# tcp-device 12.34.56.78 4001 KISS
+# timeout 15m # 15 minutes
+# #alias RELAY,WIDE,TRACE
+# <kiss-subif 0>
+# callsign N0CALL-12
+# tx-ok false
+# </kiss-subif>
+#</interface>
+#
+#<interface>
+# tcp-device 12.34.56.78 4002 TNC2
+# timeout 5m # 5 minutes
+# #alias RELAY,WIDE,TRACE
+# <kiss-subif 0>
+# callsign N0CALL-12
+# tx-ok false
+# </kiss-subif>
+#</interface>
+
+
+# *********** Multiple <beacon> definitions follow *********
+
+<beacon>
+#
+# Beacons are sent out to radio transmitters AND/OR APRSIS.
+# Default is "both", other modes are settable.
+#
+#beaconmode { aprsis | both | radio }
+#
+# Beacons are sent from a circullar transmission queue, total cycle time
+# of that queue is 20 minutes by default, and beacons are "evenly"
+# distributed along it. Actual intervals are randomized to be anything
+# in between 80% and 100% of the cycle-size / number-of-beacons.
+# First beacon is sent out 30 seconds after system start.
+# Tune the cycle-size to be suitable to your number of defined beacons.
+#
+#cycle-size 20m
+#
+#
+# Basic beaconed thing is positional message of type "!":
+#
+#beacon symbol "R&" lat "0000.00N" lon "00000.00E" comment "Rx-only iGate"
+#beacon symbol "R&" $myloc comment "Rx-only iGate"
+#
+# Following are basic options:
+# 'symbol' no default, must be defined!
+# 'lat' coordinate latitude: ddmm.mmN (no default!)
+# 'lon' coordinate longitude: dddmm.mmE (no default!)
+# '$myloc' coordinate values taken from global 'myloc' entry,
+# and usable in place of explicit 'lat'+'lon'.
+# 'comment' optional tail part of the item, default is nothing
+#
+# Sample symbols:
+# R& is for "Rx-only iGate"
+# I& is for "Tx-iGate"
+# /# is for "Digipeater"
+# I# is for "Tx-iGate + Digipeater""
+#
+# Additional options are:
+# 'srccall' parameter sets claimed origination address.
+# 'dstcall' sets destination address, default "APRXnn"
+# 'interface' parameter picks an interface (must be "tx-ok true" type)
+# 'via' sets radio distribution pattern, default: none.
+# 'timefix' On APRS messages with HMS timestamp (hour:min:sec), the
+# system fixes appropriate field with transmit time timestamp.
+#
+# Message type is by default '!', which is positional no timestamp format.
+# Other possible formats are definable with options:
+# 'type' Single character setting type: ! = / @, default: !
+# 'item' Defines a name of Item (')') type beacons.
+# 'object' Defines a name of Object (';') type beacons.
+#
+# 'file' option tells a file at which a _raw_ APRS message content is
+# expected to be found as first line of text. Line ending newline
+# is removed, and no escapes are supported. The timefix is
+# available, though probably should not be used.
+# No \-processing is done on read text line.
+#
+# 'exec' option tells a computer program which returns to stdout _raw_ APRS
+# message content without newline. The timefix is
+# available, though probably should not be used.
+# No \-processing is done on read text line.
+# 'timeout' defines number of seconds the exec:ed program has to produce
+# a single text line of APRS data + ending newline, until it is
+# considered overdue and will be killed + processing moves to next
+# beacon item.
+#
+# The parameter sets can vary:
+# a) 'srccall nnn-n dstcall "string" symbol "R&" lat "ddmm.mmN" lon "dddmm.mmE" [comment "any text"]
+# b) 'srccall nnn-n dstcall "string" symbol "R&" $myloc [comment "any text"]
+# c) 'srccall nnn-n dstcall "string" raw "string"'
+#
+# The a) form flags on some of possible syntax errors in parameters.
+# It will also create only "!" type messages. The dest parameter
+# defaults to "APRS", but can be used to give other destinations.
+# The via parameter can be used to add other keywords, like "NOGATE".
+#
+# Writing correct RAW format beacon message is very hard,
+# which is evidenced by the frequency of bad syntax texts
+# people so often put there... If you can not be persuaded
+# not to do it, then at least VERIFY the beacon result on
+# web service like findu.com, or aprs.fi
+#
+# Do remember that the \ -character has special treatment in the
+# Aprx configuration parser. If you want a '\' on APRS content,
+# then you encode it on configuration file as: '\\'
+#
+# Stranger combinations with explicite "transmit this to interface X":
+#
+#
+#beacon file /tmp/wxbeacon.txt
+#beacon interface N0CALL-3 srccall N0CALL-3 \
+# raw "!0000.00NR00000.00E&aprx - an Rx-only iGate"
+#beacon interface $mycall symbol "R&" lat "0000.00N" lon "00000.00E" \
+# comment "aprx - an Rx-only iGate"
+#beacon interface $mycall symbol "R&" $myloc \
+# comment "aprx - an Rx-only iGate"
+#beacon interface $mycall symbol "I&" $myloc \
+# comment "Tx-iGate"
+#
+</beacon>
+
+# *********** <telemetry> definition(s) follow *********
+#
+# The system will always send telemetry for all of its interfaces
+# to APRSIS, but there is an option to define telemetry to be sent
+# to radio channel by using following sections for each transmitter
+# that is wanted to send out the telemetry.
+#
+# transmitter - callsign referring to <interface>
+# via - optional via-path, only 1 callsign!
+# source - one or more of <interface> callsigns for which
+# the telemetry transmission is wanted for
+#
+#<telemetry>
+# transmitter $mycall
+# via TRACE1-1
+# source $mycall
+#</telemetry>
+
+
+#<rx-igate> ## FIXME: to be written
+# AX.25 filters block selected messages matching on selected regular
+# expressions. The expressions are case sensitive, and AX.25 address
+# elements are in all uppercase text. There can be unlimited number
+# of patterns, type fields are four: "source", "destination", "via",
+# and "data". These patterns can be used in addition to built-in
+# hard-coded reject rules listed in documentation.
+#
+# ax25-reject-filter source "^NOCALL"
+# ax25-reject-filter destination "^NOCALL"
+# ax25-reject-filter via "^NOGATE"
+# ax25-reject-filter data "^\\?"
+#
+# Source interfaces from which the IGATE functionality feeds the data out
+# By default it feeds from all configured <interface>s.
+#
+# source IFCALL-1
+# source IFCALL-2
+#</rx-igate>
+
+
+# *********** Multiple <digipeater> definitions follow *********
+
+# The digipeater definitions tell transmitters that receive
+# AX.25 packets from possibly multiple sources, and then what
+# to do on the AX.25 headers of those messages.
+#
+# There is one transmitter per digipeater -- and inversely, there
+# can be at most one digipeater for each transmitter.
+#
+# In each digipeater there is at least one <source>, usually same
+# as the transmitter. You may use same <source> on multiple
+# <digipeater>s. Using multiple instances of same <source> on
+# a single <digipeater> does not crash the system, but it can cause
+# packet duplication in case of non-APRS protocols (like AX.25 CONS)
+#
+# Use only at most two levels of viscous-delay in your <digipeater>.
+# Immediate sending is by "0", and a delayed sending is any value
+# from 1 to 9. This system does not correctly support other than
+# immediate sending and one level of delay.
+#
+# Note: In order to igate correct when multiple receivers and
+# transmitters are used on single channel, the <interface>
+# definitions of each radio port must have associated
+# "igate-group N" parameter which has N of value 1 to 3.
+# See the aprx-manual.pdf for details.
+# (Default software compilation allows you to have up to
+# three channels of APRS operation.)
+#
+#
+#<digipeater>
+# transmitter TXCALL-1
+# #ratelimit 60 120 # default: average 60 packets/minute,
+# # # burst max 120 packets/minute
+# #srcratelimit 10 20 # Example: by sourcecall:
+# # average 10 packets/minute,
+# # burst max 20 packets/minute
+#
+# #<trace>
+# # maxreq 4
+# # maxdone 4
+# # keys TRACE,WIDE,RELAY
+# #</trace>
+# #<wide>
+# # maxreq 4
+# # maxdone 4
+# # keys WIDE
+# #</wide>
+#
+# <source>
+# interface TXCALL-1
+# # ratelimit 60 120 # default: average 60 packets/minute,
+# # # burst max 120 packets/minute
+# # #relay-type digipeat # default mode is "digipeat"
+# # viscous-delay 0 # default: no viscous delay
+# # #regex-filter source "RE-pattern" # can define multiple patterns
+# # #regex-filter destination "RE-pattern" # --"--; generic
+# # #regex-filter via "RE-pattern" # --"--; generic VIA
+# # #regex-filter data "RE-pattern" # --"--; APRS payload
+# # ##filter "javAPRSSrvr adjunct filters"
+# </source>
+#
+# #<source> # Extra receiver(s)
+# # interface RXCALL-1
+# # ratelimit 60 120 # default: average 60 packets/minute,
+# # # burst max 120 packets/minute
+# # #relay-type digipeat # default mode is "digipeat"
+# # #regex-filter source "RE-pattern" # can define multiple patterns
+# # #regex-filter destination "RE-pattern" # --"--; generic
+# # #regex-filter via "RE-pattern" # --"--; generic VIA
+# # #regex-filter data "RE-pattern" # --"--; APRS payload
+# # ##filter "javAPRSSrvr adjunct filters"
+# #</source>
+#
+# #<source> # APRSIS source makes this tx-igate
+# # interface APRSIS
+# # ratelimit 60 120 # default: average 60 packets/minute,
+# # # burst max 120 packets/minute
+# # relay-type third-party # Must define this for APRSIS source!
+# # viscous-delay 5 # recommendation: 5 seconds delay to give
+# # # RF delivery time make itself known.
+# # #via-path WIDE2-2 # You can define a via-path for this source
+# #</source>
+#
+# #<source> # DPRS source makes this DPRS->APRS RF gate
+# # interface DPRS
+# # ratelimit 60 120 # default: average 60 packets/minute,
+# # # burst max 120 packets/minute
+# # relay-type third-party # Must define this for DPRS source!
+# # #viscous-delay 5 # recommendation: 5 seconds delay to give
+# # # RF delivery time make itself known.
+# # #via-path WIDE2-2 # You can define a via-path for this source
+# #</source>
+#
+#</digipeater>
+
diff --git a/aprx-config.xsd b/aprx-config.xsd
new file mode 100644
index 0000000..cb01cc4
--- /dev/null
+++ b/aprx-config.xsd
@@ -0,0 +1,263 @@
+<?xml version="1.0" encoding="utf-8"?> <!-- -*- xml -*- -->
+<xs:schema targetNamespace="uri:aprx:config"
+ xmlns:ax="uri:aprx:config"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns:ht="uri:apache:config"
+ elementFormDefault="qualified">
+
+ <xs:annotation>
+ <xs:documentation>
+
+ The Aprx uses following configuration schema, which took its model very much
+ from how Apache HTTPD configuration is done.
+
+ To denote elements without < .. > around them as Apache does, we use
+ "uri:apache:config" style bits, like: <ht:element>
+
+ Because Apache's configuration predates XML and schemas, there has never been
+ proper XML-like schema for it or anything alike it, nor is this intended to be
+ validatable by XML tools.
+
+ The overall syntax is line oriented, along with \ -characters at the end of line
+ as continuation markers to append next line in place of that character plus the
+ end-of-line character(s).
+
+ Anywhere (except when previous line ends with \ -character) if the first non-white-space
+ character is '#' the whole line is comment, and is discarded.
+
+ Lines containing only white-space characters are also comments and are discarded.
+
+ Presence of non-quoted '#' at the tails of parameter lines should also behave as
+ comment, but it is not universal.
+
+ Presence of extra parameter values is not always flagged as an error.
+
+ </xs:documentation>
+ </xs:annotation>
+
+
+ <xs:complexType name="AprxConfig">
+ <xs:annotation>
+ <xs:documentation>
+ The overall Aprx config contains parameter groups, and one top-level
+ parameter label.
+
+ The "mycall" parameter label must be before anything else.
+ The <interface> and <aprsis> groups follow, and
+ the rest that use those interfaces then follow.
+ The <logging> group can be anywhere.
+ Each group can occur multiple times.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:sequence>
+ <ht:element Name="mycall" type="ax:Ax25CallsignType" />
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element Name="aprsis" type="ax:AprsisGroupType" />
+ <xs:element Name="interface" type="ax:InterfaceGroupType" />
+ <xs:element Name="logging" type="ax:LoggingGroupType" />
+ </xs:choice>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element Name="telemetry" type="ax:TelemetryGroupType" />
+ <xs:element Name="digipeater" type="ax:DigipeaterGroupType" />
+ <xs:element Name="beacon" type="ax:BeaconGroupType" />
+ <xs:element Name="logging" type="ax:LoggingGroupType" />
+ </xs:choice>
+ </xs:sequence>
+ </xs:complexType>
+
+ <!--
+ Primitive data types
+ -->
+
+
+ <xs:complexType name="Interval">
+ <xs:annotation>
+ <xs:documentation>
+ The Aprx configuration has flexible time interval definitions.
+
+ A string of decimal numbers followed by multiplier character:
+
+ 12 d 2 h 20 m 24 s
+ 50 h 70 m 200 s
+ 200 s 3 h
+
+ Whatever notation is easiest to understand for the use.
+
+ The multipliers are single characters and case insensitive.
+ Embedding white-space is permitted for readability.
+ ("d" = days, "h" = hours, "m" = minutes, "s" = seconds)
+
+ </xs:documentation>
+ </xs:annotation>
+ <xs:simpleContent>
+ <xs:extension base="xs:string">
+ </xs:extension>
+ </xs:simpleContent>
+ </xs:complexType>
+
+ <xs:complexType name="Ax25CallsignType">
+ <xs:annotation>
+ <xs:documentation>
+ This is ASCII representation of AX.25 callsign.
+ Up to 6 upper-case letters (A-Z) and digits (0-9), then
+ possibly a minus ('-') and decimal number 0 to 15.
+ By convention a "-0" is never used.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:simpleContent>
+ <xs:extension base="xs:string">
+ </xs:extension>
+ </xs:simpleContent>
+ </xs:complexType>
+
+
+ <xs:complexType name="AprsisCallsignType">
+ <xs:annotation>
+ <xs:documentation>
+ This is more relaxed form of AX.25 callsign:
+ Regular expression:
+ [a-zA-Z0-9]{1,6}($|-[a-zA-Z0-9]{1,2})
+ Or verbally:
+ Up to 6 alphanumeric characters + optional
+ tail of a minus ('-') followed by one or two
+ alphanumeric characters. Also lowercase letters
+ are allowed in alphanumerics.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:simpleContent>
+ <xs:extension base="xs:string">
+ </xs:extension>
+ </xs:simpleContent>
+ </xs:complexType>
+
+
+ <!--
+ Group types
+ -->
+
+ <xs:complexType name="AprsisGroupType">
+ <xs:annotation>
+ <xs:documentation>
+ The APRSIS group defines communication parameters to APRS-IS network.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:sequence>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <ht:element Name="login" type="ax:AprsisCallsignType">
+ <xs:annotation>
+ <xs:documentation>
+ The Login parameter for APRS-IS network.
+
+ The Aprx will calculate correct authentication parameter
+ for that login value.
+ </xs:documentation>
+ </xs:annotation>
+ </ht:element>
+ <ht:element Name="server" type="ax:AprsisServerParameterType">
+ <xs:annotation>
+ <xs:documentation>
+ Defines server to which the Aprx connects for the APRS-IS
+ service.
+
+ serverhost [portnum]
+
+ Chosen servers should be regional round-robin ones, see
+ http://www.aprs2.net/
+ noam.aprs2.net
+ soam.aprs2.net
+ euro.aprs2.net
+ asia.aprs2.net
+ aunz.aprs2.net
+ or the global pool:
+ rotate.aprs2.net
+
+ You SHOULD NOT connect to any specific one, as none of
+ the servers are behind a load-balancing fail-over mechanisms.
+
+ </xs:documentation>
+ </xs:annotation>
+ </ht:element>
+ <ht:element Name="heartbeat-timeout" type="ax:IntervalType">
+ <!-- default value: 120 second -->
+ </ht:element>
+ <ht:element Name="filter" type="ax:AprsisFilterParameterType">
+ <xs:annotation>
+ <xs:documentation>
+ One white-space terminated parameter that is fed to APRS-IS
+ server immediately after a login.
+
+ If multiple filters are needed, they are defined with
+ successive 'filter' parameter entries.
+ </xs:documentation>
+ </xs:annotation>
+ </ht:element>
+ </xs:choice>
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="InterfaceGroupType">
+ <xs:annotation>
+ <xs:documentation>
+ </xs:documentation>
+ </xs:annotation>
+ </xs:complexType>
+
+ <xs:complexType name="TelemetryGroupType">
+ <xs:annotation>
+ <xs:documentation>
+ </xs:documentation>
+ </xs:annotation>
+ </xs:complexType>
+
+ <xs:complexType name="DigipeaterGroupType">
+ <xs:annotation>
+ <xs:documentation>
+ </xs:documentation>
+ </xs:annotation>
+ </xs:complexType>
+
+ <xs:complexType name="BeaconGroupType">
+ <xs:annotation>
+ <xs:documentation>
+ </xs:documentation>
+ </xs:annotation>
+ </xs:complexType>
+
+ <xs:complexType name="LoggingGroupType">
+ <xs:annotation>
+ <xs:documentation>
+ The Logging group defines how the Aprx does logs.
+ Without it, or any parameters inside it, no logging happens.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:sequence>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <ht:element Name="aprxlog" type="ax:filename">
+ </ht:element>
+ <ht:element Name="dprslog" type="ax:filename">
+ </ht:element>
+ <ht:element Name="rflog" type="ax:filename">
+ </ht:element>
+ <ht:element Name="pidfile" type="ax:filename">
+ </ht:element>
+ <ht:element Name="erlangfile" type="ax:filename">
+ </ht:element>
+ <ht:element Name="erlang-loglevel" type="ax:???">
+ </ht:element>
+ <ht:element Name="erlanglog" type="ax:???">
+ </ht:element>
+ <ht:element Name="erlang-log1min" type="ax:???">
+ </ht:element>
+ </xs:choice>
+ </xs:sequence>
+ </xs:complexType>
+
+
+ <!--
+ <xs:annotation>
+ <xs:documentation>
+ </xs:documentation>
+ </xs:annotation>
+ -->
+
+</xs:schema>
diff --git a/aprx-rxigate.conf.in b/aprx-rxigate.conf.in
new file mode 100644
index 0000000..9aa633f
--- /dev/null
+++ b/aprx-rxigate.conf.in
@@ -0,0 +1,118 @@
+#
+# Minimal sample configuration file for the APRX-2 as Rx-only iGate.
+#
+# This configuration is structured with Apache HTTPD style tags
+# which then contain subsystem parameters.
+#
+
+#
+# For simple case, you need to adjust 4 things:
+# - Mycall parameter
+# - Select correct type of interface (ax25-device or serial-device)
+# - Optionally set a beacon telling where this system is
+# - Optionally enable digipeater with or without tx-igate
+#
+
+#
+# Define the parameters in following order:
+# 1) mycall
+# 2) <aprsis> ** one
+# 3) <logging> ** one
+# 4) <interface> ** possibly multiple for each of radio receivers
+#
+
+#
+# Global macro for simplified callsign definition:
+# Usable for 99+% of cases.
+#
+
+mycall N0CALL-1
+
+<aprsis>
+#login OTHERCALL-7 # login defaults to $mycall
+
+# APRS-IS server name and optional portnumber.
+# Default port is 14580, and it should be enough for you.
+
+server rotate.aprs2.net
+
+#server euro.aprs2.net
+#server asia.aprs2.net
+#server noam.aprs2.net
+#server soam.aprs2.net
+#server aunz.aprs2.net
+
+#
+# Passcode for your callsign
+#
+passcode -1
+
+</aprsis>
+
+<logging>
+
+# rflog defines a rotatable file into which all RF-received packets
+# are logged. The host system can rotate it at any time without
+# need to signal the aprx that the file has been moved.
+#
+# rflog @VARLOG@/aprx-rf.log
+
+# aprxlog defines a rotatable file into which most important
+# events on APRS-IS connection are logged, namely connects and
+# disconnects. The host system can rotate it at any time without
+# need to signal the aprx that the file has been moved.
+#
+# aprxlog @VARLOG@/aprx.log
+
+</logging>
+
+
+# *********** Multiple <interface> definitions can follow *********
+
+# ax25-device Lists AX.25 ports by their callsigns that in Linux
+# systems receive APRS packets. If none are defined,
+# or the system is not Linux, the AX.25 network receiver
+# is not enabled. Used technologies need at least
+# Linux kernel 2.4.x
+#
+# tx-ok Boolean telling if this device is able to transmit.
+#
+
+#<interface>
+# ax25-device $mycall
+# #tx-ok false # transmitter enable defaults to false
+#</interface>
+
+
+#
+# The TNC serial options. Parameters are:
+# - /dev/ttyUSB1 -- tty device
+# - 19200 -- baud rate, supported ones are:
+# 1200, 2400, 4800, 9600, 19200, 38400
+# - 8n1 -- 8-bits, no parity, one stop-bit,
+# no other supported modes
+# - "KISS" - plain basic KISS mode
+# - "XORSUM" alias "BPQCRC" - KISS with BPQ "CRC" byte
+# - "SMACK" alias "CRC16" - KISS with real CRC
+# - "FLEXNET" - KISS with real CRC
+# - "TNC2" - TNC2 monitor format
+# - "DPRS" - DPRS (RX) GW
+#
+
+#<interface>
+# serial-device /dev/ttyUSB0 19200 8n1 KISS
+# #callsign $mycall # callsign defaults to $mycall
+# #tx-ok false # transmitter enable defaults to false
+#</interface>
+
+#<interface>
+# serial-device /dev/ttyUSB1 19200 8n1 TNC2
+# #callsign $mycall # callsign defaults to $mycall
+# #tx-ok false # TNC2 monitor can not have transmitter
+#</interface>
+
+#<interface>
+# serial-device /dev/ttyUSB1 19200 8n1 DPRS
+# callsign dprsgwcallsign # must define actual callsign
+# #tx-ok false # DPRS monitor can not do transmit
+#</interface>
diff --git a/aprx-stat.8.in b/aprx-stat.8.in
new file mode 100644
index 0000000..06284ad
--- /dev/null
+++ b/aprx-stat.8.in
@@ -0,0 +1,217 @@
+.TH aprx\-stat 8 "@DATEVERSION@"
+.LO 8
+.SH NAME
+.B aprx\-stat
+\- statistics utility for
+.BR aprx (8)
+.SH SYNOPSIS
+.B aprx\-stat
+.RB [ \-t ]
+.RB [ \-f \fI at VARRUN@/aprx.state\fR]
+.RB { \-S | \-x | \-X }
+.SH DESCRIPTION
+.B aprx\-stat
+is a statistics utility for
+.BR aprx (8)
+program.
+
+.SH OPTIONS
+The
+.B aprx\-stat
+has following runtime options:
+.TP
+.B "\-f \fI at VARRUN@/aprx.state\fR"
+Turn on verbose debugging, outputs data to STDOUT.
+.TP
+.B "\-S"
+SNMP data mode, current counter and gauge values.
+.TP
+.B "\-t"
+Use UNIX
+.I time_t
+for timestamps, instead of human readable text format.
+.TP
+.B "\-x"
+Lattest of extended historical gauge values.
+This gives for each input interface
+.RS
+.IP \(bu 2
+SNMP data
+.IP \(bu 2
+last 90 of 1 minute values,
+.IP \(bu 2
+10 of 10 minute values,
+.IP \(bu 2
+3 of 60 minute values.
+.RE
+.TP
+.B "\-X"
+Full extended historical gauge values.
+This gives all the contents of historical value data ring-buffers.
+.RS
+.IP \(bu 2
+SNMP data
+.IP \(bu 2
+1 minute resolution: 24 hours
+.IP \(bu 2
+10 minute resolution: 7 days
+.IP \(bu 2
+60 minute resolution: 3 months
+.RE
+
+.SH SNMP DATA OUTPUT
+For each interface feeding AX.25 packets and/or KISS frames to this program,
+there are following kind of
+.nf
+\fC
+SNMP /dev/ttyUSB1 798282 11088 0 0 3
+SNMP ax0 798282 11088 0 0 7
+SNMP ax1 798282 11088 0 0 94
+.fi
+.PP
+where columns are:
+.IP \(bu 2
+"SNMP" - keyword
+.IP \(bu 2
+Interface (AX.25 IF name, or serial port device name)
+.IP \(bu 2
+Received byte counter
+.IP \(bu 2
+Received frame (packet) counter
+.IP \(bu 2
+.\" Transmitted byte counter (will stay zero)
+.I Dropped
+byte counter
+.IP \(bu 2
+.\" Transmitted frame counter (will stay zero)
+.I Dropped
+frame counter
+.IP \(bu 2
+Age in seconds of last update of this statistics.
+
+.SH EXTENDED DATA OUTPUT
+Extended data output gives formatted historical periodic accumulates of interface traffic
+counters, and Erlang value estimates based on that.
+.PP
+.nf
+\fC
+
+SNMP /dev/ttyUSB1 816675 11332 0 0 15
+
+1min data
+2007-12-24 14:10 /dev/ttyUSB1 1m 374 6 0 0 0.047 0.000
+2007-12-24 14:09 /dev/ttyUSB1 1m 377 5 0 0 0.047 0.000
+2007-12-24 14:08 /dev/ttyUSB1 1m 347 5 0 0 0.043 0.000
+2007-12-24 14:07 /dev/ttyUSB1 1m 140 2 0 0 0.018 0.000
+\(bu\(bu\(bu
+
+10min data
+2007-12-24 14:10 /dev/ttyUSB1 10m 3829 55 0 0 0.048 0.000
+2007-12-24 14:00 /dev/ttyUSB1 10m 2182 28 0 0 0.027 0.000
+2007-12-24 13:50 /dev/ttyUSB1 10m 3205 44 0 0 0.040 0.000
+2007-12-24 13:40 /dev/ttyUSB1 10m 3811 50 0 0 0.048 0.000
+\(bu\(bu\(bu
+
+60min data
+2007-12-24 14:00 /dev/ttyUSB1 60m 22510 295 0 0 0.047 0.000
+2007-12-24 13:00 /dev/ttyUSB1 60m 24886 347 0 0 0.052 0.000
+\(bu\(bu\(bu
+.fi
+.PP
+The output repeats for all interfaces.
+.PP
+The SNMP dataset is given in the beginning, and described above.
+Then each extended output line has following fields:
+.IP \(bu 2
+Timestamp is two fields, date and time (in minute resolution) is in UTC.
+.IP \(bu 2
+Alternate timestamp format is UNIX
+.I time_t
+as an integer, counting seconds from epoch, and as single field.
+.IP \(bu 2
+Interface name is same as in SNMP case.
+.IP \(bu 2
+Data qualifier tells what integration period the data is valid for:
+.IR 1m ", " 10m ", " 60m .
+.IP \(bu 2
+Counter of received bytes on interface (including KISS flags etc.)
+.IP \(bu 2
+Counter of received frames.
+.IP \(bu 2
+.\" Counter of transmitted bytes on interface
+Counter of dropped bytes.
+.IP \(bu 2
+.\" Counter of transmitted frames.
+Counter of dropped frames.
+.IP \(bu 2
+Reception
+.I Erlang
+value estimate.
+.IP \(bu 2
+.\" Transmission
+Dropped bytes
+.I Erlang
+value estimate.
+.PP
+.SH TODO
+.SH BUGS
+.SH SEE ALSO
+.BR aprx (8)
+
+.SH CONFIGURATION FILE
+There is no configuration file.
+
+.SH NOTES: ERLANG
+The
+.I Erlang
+is telecom measurement of channel occupancy, and in this application sense
+it does tell how much traffic there is on the radio channel.
+.PP
+Most radio transmitters are not aware of all transmitters on channel,
+and thus there can happen a collision causing loss of both messages.
+The higher the channel activity, the more likely that collision is.
+For further details, refer to statistical mathematics books, or perhaps
+on Wikipedia.
+.PP
+In order to measure channel activity, the
+.B aprx
+program suite has these built-in statistics counter and summary estimators.
+.PP
+The
+.I Erlag
+value that the estimators present are likely somewhat
+.I underestimating
+the true channel occupancy simply because it calculates estimate of channel
+bit transmit rate, and thus a per-minute character capacity.
+It does not know true frequency of bit-stuffing events of the HDLC framing,
+nor each transmitter pre- and port frame PTT times. The transmitters need to
+stabilize their transmit oscillators in many cases, which may take up to
+around 500 ms!
+The counters are not aware of this preamble-, nor postamble-times.
+.PP
+The HDLC bit stuffing ratio is guessed to be 8.2 bits for each 8 bits of payload.
+
+
+.SH NOTES: SUID ROOT
+This program needs probably to be run as
+.I "suid\-root"
+!
+It is considered safe to do so, as this checks that the
+.B "\-f"
+parameter file is of correct "magic value", and will not try to create
+it if it does not exist, nor modify that file under any circumstances,
+nor reveal content of "wrong magic kind" of file.
+
+.SH AUTHOR
+This little piece was written by
+.I "Matti Aarnio, OH2MQK"
+during a dark and rainy fall and winter of 2007-2008 after a number
+of discussions grumbling about current breed of available software
+for APRS iGate use in Linux (or of any UNIX) platforms.
+Fall and winter 2009-2010 saw appearance of digipeater functionality.
+.PP
+Principal contributors and test users include:
+.IR "Pentti Gronlund, OH3BK" ,
+.IR "Reijo Hakala, OH1GWK" .
+Debian packaging by
+.IR "Kimmo Jukarinen, OH3GNU" .
diff --git a/aprx-stat.c b/aprx-stat.c
new file mode 100644
index 0000000..0773813
--- /dev/null
+++ b/aprx-stat.c
@@ -0,0 +1,258 @@
+/* **************************************************************** *
+ * *
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ * **************************************************************** */
+
+
+#include "aprx.h"
+
+
+int time_reset;
+int debug; /* linkage dummy */
+int erlangout;
+int epochtime;
+const char *aprxlogfile; /* linkage dummy */
+const char *mycall; /* linkage dummy */
+
+#ifdef ERLANGSTORAGE
+
+void printtime(char *buf, int buflen)
+{
+ struct tm *t = gmtime(&now.tv_sec);
+ // strftime(timebuf, 60, "%Y-%m-%d %H:%M:%S", t);
+ sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d",
+ t->tm_year+1900,t->tm_mon+1,t->tm_mday,
+ t->tm_hour,t->tm_min,t->tm_sec);
+}
+
+
+void erlang_snmp(void)
+{
+ int i;
+
+ /* SNMP data output - continuously growing counters
+ */
+
+ printf("APRX.pid %8ld\n", (long) ErlangHead->server_pid);
+ printf("APRX.uptime %8ld\n",
+ (long) (time(NULL) - ErlangHead->start_time));
+ printf("APRX.mycall %s\n", ErlangHead->mycall);
+
+ for (i = 0; i < ErlangLinesCount; ++i) {
+ struct erlangline *E = ErlangLines[i];
+
+ printf("%s", E->name);
+ printf(" %ld %ld %ld %ld %ld %ld %d\n",
+ E->SNMP.bytes_rx, E->SNMP.packets_rx,
+ E->SNMP.bytes_rxdrop, E->SNMP.packets_rxdrop,
+ E->SNMP.bytes_tx, E->SNMP.packets_tx,
+ (int) (now.tv_sec - E->last_update));
+ }
+}
+
+void erlang_xml(int topmode)
+{
+ int i, j, k, t;
+
+ /* What this outputs is not XML, but a mild approximation
+ of the data that XML version would output..
+ It is not even the whole dataset, just last 60 samples
+ of each type.
+ */
+
+
+ printf("APRX.pid %8ld\n", (long) ErlangHead->server_pid);
+ printf("APRX.uptime %8ld\n",
+ (long) (time(NULL) - ErlangHead->start_time));
+ printf("APRX.mycall %s\n", ErlangHead->mycall);
+
+ for (i = 0; i < ErlangLinesCount; ++i) {
+ struct erlangline *E = ErlangLines[i];
+ char logtime[40];
+ struct tm *wallclock;
+
+ printf("\nSNMP %s", E->name);
+ printf(" %ld %ld %ld %ld %ld %ld %d\n",
+ E->SNMP.bytes_rx, E->SNMP.packets_rx,
+ E->SNMP.bytes_rxdrop, E->SNMP.packets_rxdrop,
+ E->SNMP.bytes_tx, E->SNMP.packets_tx,
+ (int) (now.tv_sec - E->last_update));
+
+ printf("\n1min data\n");
+ k = E->e1_cursor;
+ t = E->e1_max;
+ if (topmode)
+ t = 90;
+ for (j = 0; j < t; ++j) {
+ --k;
+ if (k < 0)
+ k = E->e1_max - 1;
+ if (E->e1[k].update == 0)
+ continue;
+ if (epochtime) {
+ sprintf(logtime, "%ld",
+ (long) E->e1[k].update);
+ } else {
+ wallclock = gmtime(&E->e1[k].update);
+ strftime(logtime, sizeof(logtime),
+ "%Y-%m-%d %H:%M", wallclock);
+ }
+ printf("%s %s", logtime, E->name);
+ printf(" %2dm %5ld %3ld %5ld %3ld %5ld %3ld %5.3f %5.3f %5.3f\n",
+ 1,
+ E->e1[k].bytes_rx, E->e1[k].packets_rx,
+ E->e1[k].bytes_rxdrop, E->e1[k].packets_rxdrop,
+ E->e1[k].bytes_tx, E->e1[k].packets_tx,
+ (float) E->e1[k].bytes_rx /
+ ((float) E->erlang_capa * 60.0),
+ (float) E->e1[k].bytes_rxdrop /
+ ((float) E->erlang_capa * 60.0),
+ (float)E->e1[k].bytes_tx/((float)E->erlang_capa*60.0)
+ );
+ }
+
+
+ printf("\n10min data\n");
+ k = E->e10_cursor;
+ t = E->e10_max;
+ if (topmode)
+ t = 10;
+ for (j = 0; j < t; ++j) {
+ --k;
+ if (k < 0)
+ k = E->e10_max - 1;
+ if (E->e10[k].update == 0)
+ continue;
+ if (epochtime) {
+ sprintf(logtime, "%ld",
+ (long) E->e10[k].update);
+ } else {
+ wallclock = gmtime(&E->e10[k].update);
+ strftime(logtime, sizeof(logtime),
+ "%Y-%m-%d %H:%M", wallclock);
+ }
+ printf("%s %s", logtime, E->name);
+ printf(" %2dm %5ld %3ld %5ld %3ld %5ld %3ld %5.3f %5.3f %5.3f\n",
+ 10,
+ E->e10[k].bytes_rx, E->e10[k].packets_rx,
+ E->e10[k].bytes_rxdrop, E->e10[k].packets_rxdrop,
+ E->e10[k].bytes_tx, E->e10[k].packets_tx,
+ (float) E->e10[k].bytes_rx /
+ ((float) E->erlang_capa * 60.0),
+ (float) E->e10[k].bytes_rxdrop /
+ ((float) E->erlang_capa * 60.0),
+ (float)E->e10[k].bytes_tx/((float)E->erlang_capa*60.0)
+ );
+ }
+
+
+ printf("\n60min data\n");
+ k = E->e60_cursor;
+ t = E->e60_max;
+ if (topmode)
+ t = 3;
+ for (j = 0; j < t; ++j) {
+ --k;
+ if (k < 0)
+ k = E->e60_max - 1;
+ if (E->e60[k].update == 0)
+ continue;
+ if (epochtime) {
+ sprintf(logtime, "%ld",
+ (long) E->e60[k].update);
+ } else {
+ wallclock = gmtime(&E->e60[k].update);
+ strftime(logtime, sizeof(logtime),
+ "%Y-%m-%d %H:%M", wallclock);
+ }
+ printf("%s %s", logtime, E->name);
+ printf(" %2dm %5ld %3ld %5ld %3ld %5ld %3ld %5.3f %5.3f %5.3f\n",
+ 60,
+ E->e60[k].bytes_rx, E->e60[k].packets_rx,
+ E->e60[k].bytes_rxdrop, E->e60[k].packets_rxdrop,
+ E->e60[k].bytes_tx, E->e60[k].packets_tx,
+ (float) E->e60[k].bytes_rx /
+ ((float) E->erlang_capa * 60.0),
+ (float) E->e60[k].bytes_rxdrop /
+ ((float) E->erlang_capa * 60.0),
+ (float)E->e60[k].bytes_tx/((float)E->erlang_capa*60.0)
+ );
+ }
+
+ }
+
+
+ exit(0);
+}
+
+
+void usage(void)
+{
+ printf("Usage: aprx-stat [-t] [-f arpx-erlang.dat] {-S|-x|-X}\n");
+ exit(64);
+}
+
+int main(int argc, char **argv)
+{
+ int opt;
+ int mode_snmp = 0;
+ int mode_xml = 0;
+
+ gettimeofday(&now, NULL);
+
+ while ((opt = getopt(argc, argv, "f:StxX?h")) != -1) {
+ switch (opt) {
+ case 'f':
+ erlang_backingstore = optarg;
+ break;
+ case 'S': /* SNMP */
+ ++mode_snmp;
+ break;
+ case 'X':
+ mode_xml = 1;
+ break;
+ case 'x':
+ mode_xml = 2;
+ break;
+ case 't':
+ epochtime = 1;
+ break;
+ default:
+ usage();
+ break;
+ }
+ }
+
+ erlang_start(0); /* Open the backing-store */
+
+ if (!ErlangHead)
+ exit(1);
+
+ if (mode_snmp) {
+ erlang_snmp();
+ } else if (mode_xml == 1) {
+ erlang_xml(0);
+ } else if (mode_xml == 2) {
+ erlang_xml(1);
+ } else
+ usage();
+
+ return 0;
+}
+
+#else
+
+void printtime(char *buf, int buflen) {} /* linkage dummy */
+void aprx_syslog_init(const char *p) {}
+
+int main(int argc, char **argv)
+{
+ fprintf(stderr,"Sorry - aprx-stat program not available in system configured without ERLANGSTORAGE\n");
+ return 1;
+}
+#endif
diff --git a/aprx.8.in b/aprx.8.in
new file mode 100644
index 0000000..abd3a58
--- /dev/null
+++ b/aprx.8.in
@@ -0,0 +1,1428 @@
+.TH aprx 8 "@DATEVERSION@"
+.LO 8
+.SH NAME
+.B Aprx-2
+\- An APRS iGate application with integrated Digipeater.
+.SH SYNOPSIS
+.B aprx
+.RB [ \-d [ d [ d ]]]
+.RB [ \-e ]
+.RB [ \-i ]
+.RB [ \-v ]
+.RB [ \-V ]
+.RB [ \-l " \fIsyslogfacilityname\fR]"
+.RB [ \-f " \fI at CFGFILE@\fR]"
+.SH DESCRIPTION
+The
+.B aprx
+program is a special purpose Ham-radio application supplying
+infrastructure services for APRS protocol use.
+.PP
+A more detailed manual is available at:
+.br
+http://ham.zmailer.org/oh2mqk/aprx/aprx-manual.pdf
+.PP
+.SH FEATURES
+The Aprx begun as a receive-only APRS iGate application with minimum system
+support technology requirements.
+This version has also multi-port digipeater support, transmit iGate,
+and experimental D-PRS-to-APRS RF/Rx-iGate.
+.PP
+.IP \(bu 3
+The Aprx does not require machine to have any other software in it, than things
+in UNIX standard libc. In particular no special AX.25 libraries at all, nor
+widgets or even C++ runtime.
+.IP \(bu 3
+Important goal has been to keep R/W memory footprint as small as possible,
+and on general purpose i386 Linux a single radio port iGate+digipeater is
+now around 250 kB of R/W memory allocations.
+.IP \(bu 3
+Any UNIX (and UNIX like) platform should work for the Aprx, or be trivially ported.
+.IP \(bu 3
+The Aprx can listen "TNC2 monitor" and "KISS" speaking TNCs on any serial ports.
+.IP \(bu 3
+For Aprx the serial port can be ordinary host computer port, a USB serial port,
+or remote port on a remote server behind the internet, like cisco router AUX
+ports (port 4001, TCP STREAM without TELNET escapes.)
+.IP \(bu 3
+The Aprx does not require machine to have AX.25 protocol support internally!
+(Thus this works also on e.g. Solaris and BSD machines without PF\_AX25 sockets.)
+.IP \(bu 3
+On Linux machine with kernel internal AX.25 protocol support,
+the Aprx can listen on it with promiscuous mode and in order to use that,
+the Aprx must be started as
+.I root
+user, and be configured to list interface callsigns that APRS packets are
+coming in.
+The AX.25 socket listening is not in itself configurable, it is always exists
+in Linux systems, and related configuration parameters are ignored in other
+platforms.
+This socket listening does not need auxiliary "libax25" to function.
+.IP \(bu 3
+The Aprx program can be run without root privileges at least against remote
+serial port servers. One must change local serial port ownership or
+access-groups (if any are used) to userid that runs the program and possibly
+do several changes of file paths in configuration file beginning
+with its location (startup parameter).
+How that is done is up to the user or system integrator of this program.
+.IP \(bu 3
+The Aprx connects with one callsign-ssid pair to APRS-IS core for all received
+radio ports.
+.IP \(bu 3
+The Aprx Rx-iGate knows that messages with following tokens in AX.25 VIA
+fields are not to be relayed into APRS-IS network:
+.RS 9
+.B "RFONLY, NOGATE, TCPIP, TCPXX"
+.RE
+.IP \(bu 3
+The Aprx Rx-iGate knows that following source address prefixes are bogus and
+thus messages with them are to be junked:
+.RS 9
+.B "WIDE, RELAY, TRACE, TCPIP, TCPXX, NOCALL, N0CALL"
+.RE
+.IP \(bu 3
+The Aprx Rx-iGate Drops all
+.I query
+messages ("?").
+.IP \(bu 3
+The Aprx Rx-iGate opens up all 3rd party messages ("}"), and checks the internal
+data if it is OK to be gated out to APRS-IS.
+.IP \(bu 3
+The Aprx has built-in "Erlang monitor" mechanism that telemeters each receiving
+interface to APRS-IS. It can also syslog the interface specific channel occupancy,
+and optionally can output to STDOUT.
+.IP \(bu 3
+The Aprx (since version 1.91) can do digipeater functions.
+.IP \(bu 3
+The Aprx (since version 1.99) does have experimental D-STAR D-PRS to APRS
+gateway functionality. See the
+.I aprx-manual.pdf
+for details.
+.IP \(bu 3
+The Aprx can be run on systems without writable storage, even with very little
+memory, like on NSLU2, and OpenWrt platforms.
+The experiments have shown that a single radio Tx-iGate+digipeater works
+with less than 300 kB of writable RAM for the Aprx itself.
+Additional memory is necessary for operating system services of TCP/IP
+networking, and serial port drivers.
+.PP
+.SH OPTIONS
+The
+.B aprx
+has following runtime options:
+.TP
+.B "\-i"
+Keep the program foreground without debugging outputs.
+.TP
+.B "\-d"
+Turn on verbose debugging, outputs data to STDOUT.
+.TP
+.B "\-dd"
+the "more debug" mode shows also details of network interaction with
+the APRS-IS network service.
+.TP
+.B "\-ddd"
+the "even more debug" mode shows also detail classification of
+every kind of frame received in KISS variants.
+.TP
+.B "\-e"
+.I "Erlang output"
+prints 10 minute and 60 minute traffic accumulation byte counts, and guestimates
+on channel occupancy, alias "Erlang".
+These outputs are sent to STDOUT, which system operator may choose to log elsewere.
+This is independent if the "\-l" option below.
+.TP
+.BI "\-f " "@CFGFILE@"
+Configuration file, given path is built-in default, and can be overridden by the program runner.
+.TP
+.BR "\-l" " \fIsyslogfacilityname\fR"
+Defines
+.RB syslog (3)
+facility code used by the erlang reporter by defining its name.
+Default value is:
+.BR NONE ,
+and accepted values are:
+.BR LOG_DAEMON ,
+.BR LOG_FTP ,
+.BR LOG_LPR ,
+.BR LOG_MAIL ,
+.BR LOG_NEWS ,
+.BR LOG_USER ,
+.BR LOG_UUCP ,
+.BR LOG_LOCAL0 ,
+.BR LOG_LOCAL1 ,
+.BR LOG_LOCAL2 ,
+.BR LOG_LOCAL3 ,
+.BR LOG_LOCAL4 ,
+.BR LOG_LOCAL5 ,
+.BR LOG_LOCAL6 ,
+.BR LOG_LOCAL7 .
+That list is subject to actual facility code set in the system,
+and in any case if you specify a code that is not known, then the program
+will complain during the startup, and report it.
+This is independent of the "\-e" option above.
+.TP
+.B "\-v"
+Verbose logging of received traffic to STDOUT.
+Lines begin with reception timestamp (UNIX time_t seconds), then TAB,
+and either data as is, or with prefix byte: "*" for "discarded due to data content",
+or possibly "#" for "discarded due to APRS-IS being unreachable".
+.TP
+.B "\-V"
+Print source version compiled to this binary, and exit.
+.PP
+.SS DEBUGGING SYSTEM
+Use parameter set
+.B "\-ddv"
+(or
+.BR "\-dddv" )
+to test new configuration by running it synchronously to console.
+.PP
+.SS NORMAL OPERATION
+Running the
+.B aprx
+program without any of option flags:
+.BR "\-d" ,
+.BR "\-v" ", or"
+.B "\-e"
+reads possibly given configuration, then automatically backgrounds the process, and writes
+.IR pidfile .
+When the process whose number written in
+.I pidfile
+is then sent a SIGTERM signal, it automatically shuts down itself, and removes the
+.IR pidfile .
+The
+.I pidfile
+can be runtime configured with the
+.BI \-f " @CFGFILE@"
+file, and it has default name of:
+.IR "@VARRUN@/aprx.pid" .
+.PP
+
+.SH CONFIGURATION FILE
+The configuration file is used to setup the program to do its job.
+.PP
+You can construct following configurations:
+.PP
+.IP \(bu 3
+A
+.I receive-only
+iGate server.
+.IP \(bu 3
+A digipeater with bi-directional iGate server.
+.IP \(bu 3
+A
+.I "single radio"
+digipeater. (The most common type of digipeater.)
+.IP \(bu 3
+A
+.I multi-interfaced
+digipeater relaying traffic in between multiple radios. (On same or on separate frequencies.)
+.IP \(bu 3
+A
+.I "viscuous digipeater,"
+which relays a packet it heard from viscuous source after the viscuous delay,
+.I unless it was heard more times than only once,
+or it was heard from non-viscuous source before the viscuous one was digipeated.
+This allows of making fill-in digipeaters that will not digipeat the packet,
+if that same packet was heard twice or more before the viscuos delay expired.
+.PP
+In the configuration file a line ending backslash (\\) character concatenates
+next input line into itself. Combined result can be up to 8000 bytes long.
+This combination can be a bit surprising:
+.RS 3em
+.nf
+\fC#beacon .... long text \\
+ continuation\fR
+.fi
+.RE
+results in single long input line that begins with '#' (it is comment) and all
+continuations following it have been folded in.
+Presented line number of combined continuation is the line number of the
+.I last
+line segment in this type of multi-line input.
+.PP
+In the configuration file there is special treatment for quoted strings.
+They are stripped of the outer quotes, and "\fC\\\fR" character is processed within
+the source string to produce an output string.
+The escapes are:
+.TP
+.B "\fC\\\\n"
+Produces newline character (Control-J) on the output string.
+.TP
+.B "\fC\\\\r"
+Produces carriage return character (Control-M) on the output string.
+.TP
+.B "\fC\\\\\\\\"
+Places a back-slash on the output string.
+.TP
+.B "\fC\\\\""
+.\" foo "
+Places a double-quote on the output string.
+.TP
+.B "\fC\\\\'"
+Places a single-quote on the output string.
+.TP
+.B "\fC\\\\xHH"
+Lower-case "x" precedes two hex digits which ensemble is then converted to a single byte in the output string.
+.PP
+The complex encodings are for possible initstrings of the external devices,
+and in particular for initstrings even a nul byte ( \\x00 ) is supported.
+.PP
+A configuration token without surrounding quotes does not understand the backslash escapes.
+.PP
+.nf
+\fC
+#
+# Sample configuration file for the APRX -- an Rx-only APRS iGate with
+# Digipeater functionality.
+#
+#
+# Simple sample configuration file for the APRX-2
+#
+# This configuration is structured with Apache HTTPD style tags
+# which then contain subsystem parameters.
+#
+
+#
+# For simple case, you need to adjust 4 things:
+# - Mycall parameter
+# - Select correct type of interface (ax25-device or serial-device)
+# - Optionally set a beacon telling where this system is
+# - Optionally enable digipeater with or without tx-igate
+#
+
+#
+#
+# Define the parameters in following order:
+# 1) <aprsis> ** zero to many
+# 2) <logging> ** zero or one
+# 3) <interface> ** one to many
+# 4) <beacon> ** zero to many
+# 5) <telemetry ** zero to many
+# 6) <digipeater> ** zero to many (at most one for each Tx)
+#
+
+#
+# Global macro for simplified callsign definition:
+# Usable for 99+% of cases.
+#
+
+mycall N0CALL-1
+
+#
+# Global macro for simplified "my location" definition in
+# place of explicit "lat nn lon mm" at beacons. Will also
+# give "my location" reference for "filter m/100".
+#
+#myloc lat ddmm.mmN lon dddmm.mmE
+
+<aprsis>
+# The login parameter:
+# Station call\-id used for relaying APRS frames into APRS\-IS.
+# Use this only to define other callsign for APRS\-IS login.
+#
+#login OTHERCALL-7 # login defaults to $mycall
+
+#
+# The passcode parameter:
+# Unique code for your callsign to allow transmitting packets
+# into the APRS-IS.
+#
+passcode -1
+
+
+
+# APRS-IS server name and portnumber.
+# Every reconnect does re\-resolve the name to IP address.
+# Some alternates are shown below, choose something local to you.
+#
+server rotate.aprs2.net 14580
+#server noam.aprs2.net 14580
+#server soam.aprs2.net 14580
+#server euro.aprs2.net 14580
+#server asia.aprs2.net 14580
+#server aunz.aprs2.net 14580
+
+# Some APRS\-IS servers tell every about 20 seconds to all contact
+# ports that they are there and alive. Others are just silent.
+# Recommended value 3*"heartbeat" + some \-> 120 (seconds)
+#
+#heartbeat\-timeout 0 # Disabler of heartbeat timeout
+
+# APRS-IS server may support some filter commands.
+# See: http://www.aprs-is.net/javAPRSFilter.aspx
+#
+# You can define the filter as single long quoted string, or as
+# many short segments with explaining comments following them.
+#
+# Usability of these filters for a Tx-iGate is dubious, but
+# they exist in case you for example want to Tx-iGate packets
+# from some source callsigns in all cases even when they are
+# not in your local area.
+#
+#filter "possibly multiple filter specs in quotes"
+#
+#filter "m/100" # My-Range filter
+#filter "f/OH2XYZ\-3/50" # Friend-Range filter
+</aprsis>
+
+
+<logging>
+# pidfile is UNIX way to tell that others that this program is
+# running with given process-id number. This has compiled-in
+# default value of: pidfile @VARRUN@/aprx.pid
+#
+#pidfile @VARRUN@/aprx.pid
+
+# rflog defines a rotatable file into which all RF-received packets
+# are logged.
+#
+#rflog @VARLOG@/aprx\-rf.log
+
+# aprxlog defines a rotatable file into which most important
+# events on APRS\-IS connection are logged, namely connects and
+# disconnects.
+#
+#aprxlog @VARLOG@/aprx.log
+
+# erlangfile defines a mmap():able binary file, which stores
+# running sums of interfaces upon which the channel erlang
+# estimator runs, and collects data.
+# Depending on the system, it may be running on a filesystem
+# that actually retains data over reboots, or it may not.
+# With this backing store, the system does not loose cumulating
+# erlang data over the current period, if the restart is quick,
+# and does not stradle any exact minute.
+# (Do restarts at 15 seconds over an even minute..)
+# This file is around 0.7 MB per each interface talking APRS.
+# If this file is not defined and can not be created,
+# internal non-persistent in-memory storage will be used.
+#
+# Built-in default value is: @VARRUN@/aprx.state
+#
+#erlangfile @VARRUN@/aprx.state
+
+# erlang\-loglevel is config file edition of the "\-l" option
+# pushing erlang data to syslog(3).
+# Valid values are (possibly) following: NONE, LOG_DAEMON,
+# LOG_FTP, LOG_LPR, LOG_MAIL, LOG_NEWS, LOG_USER, LOG_UUCP,
+# LOG_LOCAL0, LOG_LOCAL1, LOG_LOCAL2, LOG_LOCAL3, LOG_LOCAL4,
+# LOG_LOCAL5, LOG_LOCAL6, LOG_LOCAL7. If the parameter value is
+# not acceptable, list of accepted values are printed at startup.
+#
+#erlang\-loglevel NONE
+
+# erlanglog defines a rotatable file into which erlang data
+# is written in text form.
+#
+#erlanglog @VARLOG@/erlang.log
+
+# erlang\-log1min option logs to syslog/file also 1 minute
+# interval data from the program. (In addition to 10m and 60m.)
+#
+#erlang\-log1min
+</logging>
+
+
+
+# *********** Multiple <interface> definitions can follow *********
+
+# ax25\-device Lists AX.25 ports by their callsigns that in Linux
+# systems receive APRS packets. If none are defined,
+# or the system is not Linux, the AX.25 network receiver
+# is not enabled. Used technologies need at least
+# Linux kernel 2.4.x
+#
+# tx\-ok Boolean telling if this device is able to transmit.
+#
+#<interface>
+# ax25\-device $mycall # Either $mycall macro, or actual callsign
+# #tx\-ok false # transmitter enable defaults to false
+# #telem\-to\-is true # set to false to disable
+#</interface>
+
+# The TNC serial options. Parameters are:
+# \- /dev/ttyUSB1 \-\- tty device
+# \- 19200 \-\- baud rate, supported ones are:
+# 1200, 2400, 4800, 9600, 19200, 38400, ...
+# \- 8n1 \-\- 8\-bits, no parity, one stop\-bit,
+# no other supported modes
+# \- "KISS" \- plain basic KISS mode
+# \- "XORSUM" alias "BPQCRC" \- KISS with BPQ "CRC" byte
+# \- "SMACK" alias "CRC16" \- KISS with real CRC
+# \- "FLEXNET" \- KISS with real CRC
+# \- "TNC2" \- TNC2 monitor format
+# \- "DPRS" \- DPRS (rx) Gateway
+#
+#<interface>
+# serial\-device /dev/ttyUSB0 19200 8n1 KISS
+# #callsign $mycall # Either $mycall macro, or actual callsign
+# #tx\-ok false # transmitter enable defaults to false
+# #telem\-to\-is true # set to false to disable
+#</interface>
+#
+#<interface>
+# serial\-device /dev/ttyUSB1 19200 8n1 TNC2
+# #callsign $mycall # Either $mycall macro, or actual callsign
+# #tx\-ok false # TNC2 monitor can not have transmitter
+# #telem\-to\-is true # set to false to disable
+#</interface>
+#
+#<interface>
+# serial\-device /dev/ttyUSB1 19200 8n1 DPRS
+# callsign dprsgwcallsign # must define actual callsign
+# #tx\-ok false # DPRS monitor can not do transmit
+# #telem\-to\-is true # set to false to disable
+#</interface>
+#
+
+
+# *********** Multiple <beacon> definitions can follow *********
+<beacon>
+#
+# Beacons are sent out to radio transmitters AND/OR APRSIS.
+# Default is "both", other modes are settable.
+#
+#beaconmode { aprsis | both | radio }
+#
+# Beacons are sent from a circullar transmission queue, total cycle time
+# of that queue is 20 minutes by default, and beacons are "evenly"
+# distributed along it. Actual intervals are randomized to be anything
+# in between 80% and 100% of the cycle-size / number-of-beacons.
+# First beacon is sent out 30 seconds after system start.
+# Tune the cycle-size to be suitable to your number of defined beacons.
+#
+#cycle-size 20m
+#
+#
+# Basic beaconed thing is positional message of type "!":
+#
+#beacon symbol "R&" lat "0000.00N" lon "00000.00E" comment "Rx-only iGate"
+#beacon symbol "R&" $myloc comment "Rx-only iGate"
+#
+# Following are basic options:
+# 'symbol' no default, must be defined!
+# 'lat' coordinate latitude: ddmm.mmN (no default!)
+# 'lon' coordinate longitude: dddmm.mmE (no default!)
+# '$myloc' coordinate values taken from global 'myloc' entry,
+# and usable in place of explicit 'lat'+'lon'.
+# 'comment' optional tail part of the item, default is nothing
+#
+# Sample symbols:
+# R& is for "Rx-only iGate"
+# I& is for "Tx-iGate"
+# /# is for "Digipeater"
+# I# is for "Tx-iGate + Digipeater"
+#
+# Additional options are:
+# 'srccall' parameter sets claimed origination address.
+# 'dstcall' sets destination address, default "APRXnn"
+# 'interface' parameter picks an interface (must be "tx-ok true" type)
+# 'via' sets radio distribution pattern, default: none.
+# 'timefix' On APRS messages with HMS timestamp (hour:min:sec), the
+# system fixes appropriate field with transmit time timestamp.
+#
+# Message type is by default '!', which is positional no timestamp format.
+# Other possible formats are definable with options:
+# 'type' Single character setting type: ! = / @
+# 'item' Defines a name of Item (')') type beacons.
+# 'object' Defines a name of Object (';') type beacons.
+#
+# 'file' option tells a file at which a _raw_ APRS message content is
+# expected to be found as first line of text. Line ending newline
+# is removed, and no escapes are supported. The timefix is
+# available, though probably should not be used.
+#
+# 'exec' option defines program path for a program whose stdout is
+# read up to first newline (which must be present), and then
+# transmit as beacon content. No format helpers are supplied,
+# although 'timefix' can be used.
+# 'timeout' option is associated with 'exec', and defines when the
+# exec must by latest produce the output, or the subprogram
+# execution is killed. Default value is 10 seconds.
+#
+# The parameter sets can vary:
+# a) 'srccall nnn-n dstcall "string" symbol "R&" lat "ddmm.mmN" lon "dddmm.mmE" [comment "any text"]
+# b) 'srccall nnn-n dstcall "string" raw "string"'
+#
+# The a) form flags on some of possible syntax errors in parameters.
+# It will also create only "!" type messages. The dest parameter
+# defaults to "APRS", but can be used to give other destinations.
+# The via parameter can be used to add other keywords, like "NOGATE".
+#
+# Writing correct RAW format beacon message is very hard,
+# which is evidenced by the frequency of bad syntax texts
+# people so often put there... If you can not be persuaded
+# not to do it, then at least VERIFY the beacon result on
+# web service like findu.com, or aprs.fi
+#
+#beacon file /tmp/wxbeacon.txt
+#beacon srccall N0CALL\-3 raw "!0000.00NR00000.00E&aprx \- an Rx\-only iGate"
+#beacon srccall N0CALL\-3 raw "!0000.00NI00000.00E&aprx \- an iGate"
+#beacon srccall $mycall symbol "R&" lat "0000.00N" lon "00000.00E" \\
+ comment "aprx \- an Rx\-only iGate"
+#beacon srccall $mycall symbol "I&" lat "0000.00N" lon "00000.00E" \\
+ comment "aprx iGate"
+</beacon>
+
+# *********** <telemetry> definition(s) follow *********
+#
+# The system will always send telemetry for all of its interfaces
+# to APRSIS, but there is an option to define telemetry to be sent
+# to radio channel by using following sections for each transmitter
+# that is wanted to send out the telemetry.
+#
+# transmitter - callsign referring to <interface>
+# via - optional via-path, only 1 callsign!
+# source - one or more of <interface> callsigns for which
+# the telemetry transmission is wanted for
+#
+#<telemetry>
+# transmitter $mycall
+# via TRACE1-1
+# source $mycall
+#</telemetry>
+
+
+# *********** <digipeater> definition(s) follow *********
+#
+# The digipeater definitions tell transmitters that receive
+# AX.25 packets from possibly multiple sources, and then what
+# to do on the AX.25 headers of those messages.
+#
+# There is one transmitter per digipeater \-\- and inversely, there
+# can be at most one digipeater for each transmitter.
+#
+# In each digipeater there is at least one <source>, usually same
+# as the transmitter.
+#
+#<digipeater>
+# transmitter $mycall
+# #ratelimit 60 120 # default: average 60 packets/minute,
+# # burst max 120 packets/minute
+# #srcratelimit 10 20 # Example: by sourcecall:
+# # average 10 packets/minute,
+# # burst max 20 packets/minute
+#
+# <source>
+# source $mycall
+# # ratelimit 60 120 # default: average 60 packets/minute,
+# # # burst max 120 packets/minute
+# # viscous\-delay 0 # no viscous delay for RF\->RF digipeat
+# # ratelimit 120 # default: max 120 packets/minute
+# </source>
+#
+# #<source> # Adding APRSIS source makes this tx-igate
+# # source APRSIS
+# # ratelimit 60 120 # default: average 60 packets/minute,
+# # # burst max 120 packets/minute
+# # relay\-type third\-party # Must define this for APRSIS source!
+# # viscous\-delay 5 # Recommendation: 5 seconds delay to give
+# # # RF delivery time make itself known.
+# # filter t/m # Tx-iGate only messages sent to me by APRSIS
+# #</source>
+#
+#</digipeater>
+\fR
+.fi
+.PP
+.SH GLOBAL MYCALL PARAMETER
+In majority of usage models, system needs single configured callsign.
+This is set by using the
+.B mycall
+configuration option, and latter referred to in configurations as
+.B $mycall
+parameter in place of callsigns.
+.PP
+.SH GLOBAL MYLOC PARAMETER
+Usually multiple beacons, and simple filter rules are wanted to be using
+same reference coordinate for this system.
+This is set by using the
+.B myloc
+configuration option, and latter referred to in configurations as
+.B $myloc
+parameter in place of "lat nn lon mm" coordinate pair of beacons.
+.SH APRSIS SECTION FOR APRSIS CONNECTIVITY
+Settings in the
+.B <aprsis>
+section define connectivity with the APRS-IS network service.
+.PP
+Necessary option is
+.IR server ,
+and others are optional.
+.PP
+Available options are:
+.IP "\fClogin $mycall\fR" 8em
+The APRSIS network login.
+Defaults to the
+.B mycall
+configuration entry.
+.IP "\fCpasscode -1\fR" 8em
+Defining a small integer in range of 0 to 32767 authenticating your login
+to APRS-IS server. Ask for assistance from your APRS-IS managers, or calculate
+it yourself with
+.I aprspass
+program. (Web search engines do find several of them.)
+.IP "\fCserver \fIserver-name 14850\fR" 8em
+Define which APRS-IS is being connected to.
+Multiple definitions are used in round-robin style,
+if the connection with the previous one fails for some reason.
+.PP
+.IP "\fCfilter \fI'filter specs in quotes'\fC # \fIcomments" 8em
+Set filter adjunct definitions on APRS-IS server.
+Multiple entries are catenated together in entry order,
+when connecting to the server.
+.PP
+.SH LOGGING SECTION
+The
+.B <logging>
+section defines miscellaneous file names and options for state tracking and logging use.
+.PP
+.IP "\fCpidfile \fI at VARRUN@/aprx.pid\fR" 8em
+The pidfile is UNIX way to tell that others that this program is
+running with given process-id number.
+This has compiled-in default value of: \fCpidfile @VARRUN@/aprx.pid
+.IP "\fCrflog \fI at VARLOG@/aprx\-rf.log\fR" 8em
+The
+.I rflog
+defines a rotatable file into which all RF-received packets are logged.
+There is no default.
+.IP "\fCaprxlog \fI at VARLOG@/aprx.log\fR" 8em
+The
+.I aprxlog
+defines a rotatable file into which most important events on APRS-IS
+connection are logged, namely connects and disconnects.
+There is no default.
+.IP "\fCerlangfile \fI at VARRUN@/aprx.state\fR" 8em
+The
+.I erlangfile
+defines a mmap():able binary file, which stores running sums of interfaces
+upon which the channel erlang estimator runs, and collects data.
+Depending on the system, it may be running on a filesystem that actually
+retains data over reboots, or it may not.
+With this backing store, the system does not loose cumulating erlang data
+over the current period, if the restart is quick, and does not stradle
+any exact minute.
+This file is around 0.7 MB per each interface talking APRS.
+If this file is not defined and can not be created,
+internal non-persistent in-memory storage will be used.
+Built-in default value is: @VARRUN@/aprx.state
+.IP "\fCerlang\-loglevel \fINONE\fR" 8em
+The
+.I erlang\-loglevel
+is config file edition of the "\-l" option pushing erlang data to
+.IR syslog (3).
+Valid values are (possibly) following: NONE, LOG_DAEMON,
+LOG_FTP, LOG_LPR, LOG_MAIL, LOG_NEWS, LOG_USER, LOG_UUCP,
+LOG_LOCAL0, LOG_LOCAL1, LOG_LOCAL2, LOG_LOCAL3, LOG_LOCAL4,
+LOG_LOCAL5, LOG_LOCAL6, LOG_LOCAL7.
+If the parameter value is not acceptable, list of accepted
+values are printed at startup.
+.IP "\fCerlanglog \fI at VARLOG@/erlang.log\fR" 8em
+The erlanglog defines a rotatable file into which erlang data
+is written in text form.
+There is no default.
+.IP "\fCerlang\-log1min\fR" 8em
+The
+.I erlang\-log1min
+option logs to syslog/file also 1 minute interval data from the program.
+(In addition to 10m and 60m.)
+Default is off.
+.PP
+.SH INTERFACE SECTIONS FOR RADIO PORTS
+The
+.B <interface>
+sections define connections to radio modems.
+Several different styles are available:
+.IP \(bu 2
+Local serial ports in the machine
+.RB ( "device\-serial /dev/ttyS0 " "\fIspeed encapsulation\fR)"
+.IP \(bu 2
+Local USB serial ports in the machine
+.RB ( "device\-serial /dev/ttyUSB0 " "\fIspeed encapsulation\fR)"
+.IP \(bu 2
+Remote served serial ports over a TCP stream.
+Implemented to talk with Cisco AUX ports on "range 4000"
+(TCP STREAM, no TELNET escapes)
+.RB ( "tcp\-device 12.34.56.78 4001 " "\fIencapsulation\fR)"
+.IP \(bu 2
+Linux internal AX.25 network attached devices
+.RB ( "ax25\-device CALLSIGN\-1" )
+are only available when running on a Linux system.
+On a non-Linux system it connects to a null interface, never
+getting anything and can always sink everything.
+.PP
+The serial port name tells what kind of port is in question,
+and while port baud-rate (9600) and character settings (8n1)
+must always be set, they are ignored for the remote connection.
+.PP
+Following
+.I speed
+modes are available:
+.br
+.B " " 1200,
+.I 1800,
+.B 2400, 4800, 9600, 19200,
+.I 38400, 57600,
+.br
+.I " " 115200, 230400, 460800, 500000, 576000
+.br
+Likely available speeds are in bold, other supported values
+are listed in italics.
+.PP
+Following
+.I encapsulation
+modes are available:
+.TP 10em
+.B TNC2
+is capable only to monitor the packets reported by TNC2 type
+debug output, and Rx-iGate, but they are not acceptable as
+source for a <digipeater>.
+.TP 10em
+.B DPRS
+is special mode for gateway from D-STAR D-PRS to APRS.
+This must always have a callsign definition for the gateway.
+.TP 10em
+.B KISS
+Basic KISS encapsulation.
+No checksums.
+Will autodetect (sometimes) packets with SMACK or FLEXNET characteristics.
+.TP 10em
+.B SMACK
+.IR "Stuttgart Modified Amateurradio-CRC-KISS" ,
+which runs CRC-16 checksum on KISS datastream much in the same
+way as HDLC has CCITT-CRC checksum on it.
+.TP 10em
+.B FLEXNET
+.IR "FLEXNET"
+which runs a CRC checksum of its own polynomial on KISS datastream
+much in the same way as HDLC has CCITT-CRC checksum on it.
+.TP 10em
+.B BPQCRC
+XOR "checksum" on dataframes.
+Also known as "XKISS", and "XORSUM".
+This detects single bit failure, but weakly any multibit failures.
+Extra 0x00 bytes have no effect on checksum, etc.
+.PP
+On
+.BI "<kiss\-subif " "tncid" ">"
+sub-options the parameter is
+.IR tncid ,
+which sets up KISS multiplexer parameter so that subsequent
+options applies only on designated KISS sub-port.
+.PP
+The
+.I callsign
+option sets port specific callsign when relaying to APRS-IS.
+.PP
+The
+.I "telem\-to\-is true"
+option can be used to disable (by explicitly setting it to 'false')
+radio interface telemetry transmission to APRS-IS.
+By default it is on.
+This is separate from <telemetry> sections, which send telemetry
+to RF interfaces.
+.PP
+.nf
+\fC<interface>
+ serial\-device /dev/ttyUSB1 19200 8n1 KISS
+ tx\-ok false # receive only (default)
+ callsign OH2XYZ\-R2 # KISS subif 0
+ initstring "...." # initstring option
+ timeout 900 # 900 seconds of no Rx
+</interface>
+
+<interface>
+ serial\-device /dev/ttyUSB1 19200 8n1 SMACK
+ tx\-ok false # receive only (default)
+ callsign OH2XYZ\-R2 # KISS subif 0
+ initstring "...." # initstring option
+ timeout 900 # 900 seconds of no Rx
+</interface>
+
+<interface>
+ serial\-device /dev/ttyUSB2 19200 8n1 KISS
+ initstring "...."
+ timeout 900 # 900 seconds of no Rx
+ <kiss\-subif 0>
+ callsign OH2XYZ\-2
+ tx\-ok true # This is our transmitter
+ </kiss\-subif>
+ <kiss\-subif 1>
+ callsign OH2XYZ\-R3 # This is receiver
+ tx\-ok false # receive only (default)
+ </kiss\-subif>
+</interface>
+
+<interface>
+ tcp\-device 172.168.1.1 4001 KISS
+ tx\-ok false # receive only (default)
+ callsign OH2XYZ\-R4 # KISS subif 0
+ initstring "...." # initstring option
+ timeout 900 # 900 seconds of no Rx
+</interface>
+
+<interface>
+ ax25\-device OH2XYZ\-6 # Works only on Linux systems
+ tx\-ok true # This is also transmitter
+</interface>
+
+<interface> # \fBRX-IGATE ONLY, NOT USABLE AS DIGIPEATER SOURCE\fR\fC
+ serial\-device /dev/ttyUSB1 19200 8n1 TNC2
+ callsign OH2XYZ\-R6 # TNC2 has no sub-ports
+ initstring "...." # initstring option
+ timeout 900 # 900 seconds of no Rx
+</interface>
+.fi
+
+.SH BEACON DEFINITIONS
+The beacons are defined using
+.B <beacon>
+configuration sections.
+.PP
+Because classical beacon definitions are highly error\-prone, this program
+has a new way to define them:
+.IP \(bu 2
+The new way to define beacons:
+.nf
+\fCbeacon symbol "R&" lat "0000.00N" lon "00000.00E" \\\fR
+\fC comment "aprx \- iGate" \fR
+.fi
+.IP \(bu 2
+Semi-clasical definition of raw APRS packet:
+.nf
+\fCbeacon raw "!0000.00NR00000.00E&aprx \- iGate"\fR
+.fi
+.IP \(bu 2
+Load beacon text from a file, path data is configurable:
+.nf
+\fCbeacon file \fR\fI/path/to/file\fR
+.fi
+.IP \(bu 2
+Run a program to produce beacon data in raw format:
+.nf
+\fCbeacon exec \fR\fI/path/to/file\fR\fC timeout \fR\fI10\fR
+.fi
+.PP
+The fields and parameters:
+.TP 12em
+.B interface
+An
+.I optional
+"interface" parameter tells that this beacon shall be sent only to
+interface whose callsign is named.
+Default is to send to all interfaces that have "tx\-ok true" setting.
+
+.TP 12em
+.B type
+An
+.I optional
+one character string parameter, with one of following
+contents: "!", "=", "/", "@", ";" and ")".
+
+.TP 12em
+.B srccall
+An
+.I optional
+"srccall" parameter tells callsign which is claimed as this particular
+beacon source.
+It must be valid AX.25 callsign in text format.
+When this "srccall" parameter is not given, value of "mycall" configuration
+entry is used.
+.TP 12em
+.B dstcall
+An
+.I optional
+"dstcall" parameter has built-in software version dependent value,
+but it can be used to define another value.
+.TP 12em
+.B via
+An
+.I optional
+"via" parameter defaults to nothing, but can be used to define additional
+"VIA" path tokens, for example: "WIDE1\-1".
+.TP 12em
+.B item
+An
+.I optional
+"item" parameter is for defining a name for an item type APRS packet.
+.TP 12em
+.B object
+An
+.I optional
+"object" parameter is for defining a name for an object type APRS packet.
+.TP 12em
+.B symbol
+A
+.I mandatory
+"symbol" parameter is two character code, which for Rx-only iGate is pair: "R&"
+.TP 12em
+.B lat
+This
+.I mandatory
+parameter defines
+.I latitude
+coordinate (that is: north/south.)
+It is expected to be of format: "ddmm.mmN" where "dd" defines
+.I two digits
+of
+.I degrees
+of latitude, and "mm.mm" defines two digits + decimal dot + two digits of
+.I minutes
+of latitude.
+Then comes literal "N" or "S" indicating hemisphere.
+.TP 12em
+.B lon
+This
+.I mandatory
+parameter defines
+.I longitude
+coordinate (that is: east/west.)
+It is expected to be of format: "dddmm.mmE" where "ddd" defines
+.I three digits
+of
+.I degrees
+of longitude, and "mm.mm" defines two digits + decimal dot + two digits of
+.I minutes
+of longitude.
+Then comes literal "E" or "W" indicating hemisphere.
+.TP 12em
+.B comment
+This
+.I optional
+parameter defines commentary text tail on the beacon packet.
+If you need characters outside US-ASCII character set, use of UTF-8 encoded
+UNICODE character set is recommended.
+
+.TP 12em
+.B raw
+This
+.I alternate
+format defines whole APRS packet content in raw text format.
+.I Currently this type of packets are not validated for syntax at all!
+.TP 12em
+.B file
+This
+.I alternative
+way defines path to a file with single text line defining
+content of
+.I raw
+message data.
+.TP 12em
+.B exec
+This
+.I alternative
+mode runs designated program, and waits for at most a
+.I timeout
+number of seconds (default 10) for the program to produce the result.
+.TP 12em
+.B timeout
+This is optional parameter for
+.I exec
+allowing altered timeout (number of seconds) for waiting the program to respond.
+Default is 10 seconds.
+.PP
+The type/symbol/lat/lon/comment-format supports only
+a few types of APRS packets.
+It splits input into small slices that are possible
+to validate in detail.
+(See "DEBUGGING SYSTEM" above.)
+.PP
+.SH RF-TELEMETRY
+The
+.I aprx
+system will always send telemetry for all of its interfaces
+to APRSIS, but there is an option to define telemetry to be sent
+to radio channel by using following sections for each transmitter
+that is wanted to send out the telemetry.
+.PP
+The parameters of
+.B <telemetry>
+configuration section are:
+.TP 12em
+.B transmitter
+A mandatory callsign referring to an
+.I interface.
+.TP 12em
+.B via
+An optional
+.I via-path
+parameter. Only 1 callsign!
+.TP 12em
+.B source
+One or more of
+.I interface
+callsigns for which the telemetry transmission is made.
+.SH DIGIPEATER
+The
+.I aprx
+is possible to configure as a AX.25 digipeater with APRS twists.
+This is done with
+.B <digipeater>
+configuration section and its subsections.
+.PP
+.I There can be at most one <digipeater> definition per each
+.I transmit capable interface in the system.
+.I On a system with multiple transmitters, this means there can
+.I be multiple digipeaters, each with different behaviour rules.
+.PP
+Minimalistic setup for a digipeater will be as follows:
+.PP
+.nf
+\fC<digipeater>
+ transmitter $mycall
+ <source>
+ source $mycall
+ </source>
+</digipeater>\fR
+.fi
+.PP
+In minimalistic approach the system does digipeating of packets heard
+on the
+.I $mycall
+interface back to same interface.
+Single requirement is that the
+.I <interface>
+block has
+.I "tx\-ok true"
+setting on it.
+.PP
+In more complicated approaches it is possible to define multiple sources
+for packets:
+.IP \(bu 3
+Multiple device ports.
+.IP \(bu 3
+APRSIS pseudoport, which creates the Tx-iGate functionality.
+.PP
+.SS <digipeater> options
+Main-level <digipeater> options are:
+.PP
+.IP \(bu 3
+.I transmitter
+defines which interface the digipeater will output to.
+.IP \(bu 3
+.IR <trace> " and " <wide>
+sub-options are explained below.
+.IP \(bu 3
+.I <source>
+sub-option is explained below.
+.PP
+.SS <trace> and <wide> sub-options
+The
+.I <trace>
+sub-option has priority over the
+.I <wide>
+sub-option, otherwise they are configured the same way.
+.PP
+The
+.I <trace>
+sub-option defines which AX.25 address contained keywords
+are treated with APRS "New-N paradigm" rules in a way
+where each processing node always marks its transmitter
+callsign on the transmitted AX.25 packet address header.
+.PP
+The
+.I <wide>
+sub-option defines which AX.25 address contained keywords
+are treated with APRS "New-N paradigm" rules in a way where
+processing node does not mark its transmitter callsign
+on the transmitted AX.25 packet address header.
+.PP
+Available parameters are:
+.TP 9em
+.B keys
+A string of comma-separated set of string tokens:
+.br
+\fC keys "TRACE,WIDE"\fR
+.br
+Alternative form for this entry is:
+.br
+\fC keys "TRACE"\fR
+.br
+\fC keys "WIDE"\fR
+.TP 9em
+.B maxdone
+Defines maximum number of redistribution hops that these keywords
+can have completed when reaching here.
+If accounting finds more done, the system will just drop the packet
+instead of digipeating it onwards.
+.TP 9em
+.B maxreq
+Defines maximum number of redistribution hops that these keywords
+can define.
+If accounting finds more requested, the system will just drop
+the packet instead of digipeating it onwards.
+.PP
+.SS <source> sub-options
+Primary definer option is
+.B source
+which gives callsign of an
+.I <interface>
+from which the AX.25 packets are received for this
+.I <source>
+block.
+.PP
+Available
+.B relay\-type
+modes on <source> definitions are:
+.TP 14em
+.B digipeater
+Normal AX.25 digipeater behaviour with APRS New-N paradigm support.
+This is default mode.
+.TP 14em
+.B directonly
+Digipeat only directly heard packets.
+Useful for systems that are designated as "fill-in".
+See also "viscous\-delay".
+.TP 14em
+.B third\-party
+Special mode for Tx-iGate.
+.PP
+The
+.B ratelimit
+defines two parameters:
+.I average
+and
+.I limit
+number of packets sent in 60 seconds.
+Its definitions can be both in
+.I <digipeater>
+and in
+digipeater's
+.I <source>
+sections, and therefore you can limit each individual source to
+a max accepted rate as well as define separate rate limits for
+the transmitter.
+.PP
+The
+.B viscous\-delay
+defines a number of seconds from 0 (default) maximum of 9 that
+the source will put the message on duplicate detector delay processing.
+All occurrances of same packet per duplicate detector during that time
+will be accounted on duplicate detection, and if at the end of the delay
+period there are more than one hit, the packet is discarded.
+Use delay of 0 seconds for normal digipeater, 5 seconds for a fill-in,
+or a Tx-iGate.
+.PP
+A javAPRSSrvr filter-adjunct style rules are possible with the
+.B filter
+options.
+When you want multiple filters, use multiple options with associated parameters:
+.nf
+\fC
+ filter t/m # APRS messaging type packets
+ filter a/la/lo/la/lo # APRS positional packets within this area
+\fR
+.fi
+.LP
+Also negative filters are possible (prefixed with minus character),
+which upon match cause rejection of the packet.
+Filters are evaluated in definition order, and first matching one will
+terminate the evaluation.
+When no filters are defined, everything is passed thru.
+When any filter is defined, only those matching non-negative filters
+are passed thru, and no default "pass everything else" behaviour exists.
+.LP
+Supported "adjunct filters" are following:
+.TP 8em
+.B A/latN/lonW/latS/lonE
+Area filter, defined as area enclosing within latS/latN and lonW/lonE.
+Latitude and longitude are entered as degrees and decimals.
+.TP 8em
+.B B/call1/call2...
+Budlist filter. Supports *-wildcards.
+.TP 8em
+.B D/digi1/digi2...
+.I Not supported at APRX internal filters
+.TP 8em
+.B E/call1/call2/...
+.I Not supported at APRX internal filters
+.TP 8em
+.B F/call/dist_km
+Great-circle distance in kilometers from friend's coordinates.
+No wildcarding.
+.br
+.I (TODO: check that it really works!)
+.TP 8em
+.B M/dist
+The
+.I "range around my location"
+filter requires that you have defined also the "myloc" configuration entry.
+It defines acceptance of positions and messages with senders within
+.I dist
+kilometers of the "myloc" position.
+.TP 8em
+.B O/object1/obj2...
+Object name filter. Supports *-wildcards.
+.TP 8em
+.B P/aa/bb/cc...
+Prefix filter.
+.TP 8em
+.B Q/con/ana
+.I The Q-construct filter is not supported.
+.TP 8em
+.B R/lat/lon/dist
+Range filter.
+Latitude and longitude are in degrees and decimals.
+Distance is in kilometers.
+No wildcards.
+.TP 8em
+.B S/pri/alt/over
+Symbol filter
+.TP 8em
+.B T/..../call/km
+.RS
+Type filter.
+Couple possible usages:
+.IP "\fC -t/c\fR" 22em
+Everything except CWOP
+.IP "\fC t/*/OH2RDY/50\fR" 22em
+Everything within 50 km of OH2RDY's last known position
+.PP
+Type code characters are:
+.TP 3em
+.B *
+An "all" wild-card.
+.TP 3em
+.B C
+A CWOP.
+.TP 3em
+.B I
+An ITEM.
+.TP 3em
+.B M
+A MESSAGE.
+.TP 3em
+.B N
+A NWS message.
+.TP 3em
+.B O
+An OBJECT.
+.TP 3em
+.B Q
+A QUERY.
+.TP 3em
+.B S
+A STATUS response.
+.TP 3em
+.B T
+A TELEMETRY packet or parameter message.
+.TP 3em
+.B U
+A USERDEF message.
+.TP 3em
+.B W
+A WX data packet
+.RE
+.TP 8em
+.B U/unproto1/unproto2...
+Filters by value in destination address field, supports wildcard.
+.PP
+The
+.B <trace>
+and
+.B <wide>
+sub-options exist also within each <source>.
+Where such occur, the <source> specific <trace> sub-option
+trumps the definition on <digipeater> level, and same with
+<wide> sub-options.
+This allows things like overriding flooding control keywords
+on source basis, should such be necessary.
+.PP
+A set of
+.B regex\-filter
+rules can be used to reject packets that are not of approved kind.
+Available syntax is:
+.IP "regex\-filter source RE"
+source address field
+.IP "regex\-filter destination RE"
+destination address field
+.IP "regex\-filter via RE"
+any via path field
+.IP "regex\-filter data RE"
+payload content
+.PP
+The regex\-filter exists as ad-hoc method when all else fails.
+.PP
+.SH NOTES: ERLANG
+The
+.I Erlang
+is telecom measurement of channel occupancy, and in this application sense
+it does tell how much traffic there is on the radio channel.
+.PP
+Most radio transmitters are not aware of all transmitters on channel,
+and thus there can happen a collision causing loss of both messages.
+The higher the channel activity, the more likely that collision is.
+For further details, refer to statistical mathematics books, or perhaps
+on Wikipedia.
+.PP
+In order to measure channel activity, the
+.B aprx
+program suite has these built-in statistics counter and summary estimators.
+.PP
+The
+.I Erlag
+value that the estimators present are likely somewhat
+.I underestimating
+the true channel occupancy simply because it calculates estimate of channel
+bit transmit rate, and thus a per-minute character capacity.
+It does not know true frequency of bit-stuffing events of the HDLC framing,
+nor each transmitter pre- and port frame PTT times. The transmitters need to
+stabilize their transmit oscillators in many cases, which may take up to
+around 500 ms!
+The counters are not aware of this preamble-, nor postamble-times.
+.PP
+The HDLC bit stuffing ratio is guessed to be 1:1.025 (1 extra bit every 5 bytes)
+
+.SH NOTES: PROGRAM NAME
+Initially this program had name
+.IR aprsg-ng ,
+which was too close to another (a less low-tech C++ approach) program had.
+
+.SH BUGS/WARTS
+The
+.IR Erlang -monitor
+mechanisms are of rudimentary quality, and can seriously underestimate
+the channel occupancy by ignoring pre- and postample transmissions,
+which can be as high as 50 centiseconds for preample, and 20 centiseconds
+for postample!
+When entire packet takes 50 centiseconds, such preample alone doubles channel
+occupancy.
+A 6pack protocol on serial link (instead of KISS) could inform receiver
+better on carrier presense times, however even that underestimates RF power
+presense (RSSI) signal. (6pack is not supported.)
+.PP
+On serial lines supports really only 8n1 mode, not at all like: 7e1.
+On the other hand, there really is no sensible usage for anything but 8n1...
+
+.SH SEE ALSO
+Couple web sites:
+.br
+.IR "http://www.aprs2.net/" ,
+.br
+.IR "http://www.aprs-is.net/" ,
+.br
+.IR "http://wiki.ham.fi/Aprx.en" ,
+.br
+.I "http://ham.zmailer.org/oh2mqk/aprx/aprx-manual.pdf"
+.PP
+.BR aprx-stat (8)
+
+.SH AUTHOR
+This little piece was written by
+.I "Matti Aarnio, OH2MQK"
+during a dark and rainy fall and winter of 2007-2008 after a number
+of discussions grumbling about current breed of available software
+for APRS iGate use in Linux (or of any UNIX) platforms.
+Fall and winter 2009-2010 saw appearance of digipeater functionality.
+.PP
+Principal contributors and test users include:
+.IR "Pentti Gronlund, OH3BK" ,
+.IR "Reijo Hakala, OH1GWK" .
+Debian packaging by
+.IR "Kimmo Jukarinen, OH3GNU" .
+Testing of SMACK variant of KISS by
+.IR "Patrick Hertenstein, DL1GHN" .
+The beacon exec functionality prototype by
+.IR "Kamil Palkowiski SQ8KFH" .
diff --git a/aprx.c b/aprx.c
new file mode 100644
index 0000000..7e1bc7d
--- /dev/null
+++ b/aprx.c
@@ -0,0 +1,635 @@
+/* **************************************************************** *
+ * *
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ * **************************************************************** */
+#include "aprx.h"
+
+/* Bits used only in the main program.. */
+#include <signal.h>
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+#ifdef HAVE_TIME_H
+# include <time.h>
+#endif
+#include <fcntl.h>
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+int debug;
+int verbout;
+int erlangout;
+const char *rflogfile;
+const char *aprxlogfile;
+const char *mycall;
+float myloc_lat;
+float myloc_coslat;
+float myloc_lon;
+const char *myloc_latstr;
+const char *myloc_lonstr;
+
+const char *tocall = "APRX28";
+const uint8_t tocall25[7] = {'A'<<1,'P'<<1,'R'<<1,'X'<<1,'2'<<1,'8'<<1,0x60};
+
+#ifndef CFGFILE
+#define CFGFILE "/etc/aprx.conf"
+#endif
+
+const char *pidfile = VARRUN "/aprx.pid";
+
+int die_now;
+int log_aprsis;
+
+const char *swname = "aprx";
+const char *swversion = APRXVERSION;
+
+
+static void sig_handler(int sig)
+{
+ die_now = 1;
+ signal(sig, sig_handler);
+ if (debug) {
+ // Avoid stdio FILE* interlocks within signal handler
+ char buf[64];
+ sprintf(buf, "SIGNAL %d - DYING!\n", sig);
+ write(1, buf, strlen(buf));
+ }
+}
+
+static void sig_child(int sig)
+{
+ int status;
+ int pid;
+ while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
+ beacon_childexit(pid);
+ }
+}
+
+static void usage(void)
+{
+ printf("aprx: [-d[d[d]]][-e][-i][-v][-L][-l logfacility] [-f %s]\n",
+ CFGFILE);
+ printf(" version: %s\n", swversion);
+ printf(" -f %s: where the configuration is\n", CFGFILE);
+ printf(" -v: Outputs textual format of received packets, and data on STDOUT.\n");
+ printf(" -e: Outputs raw ERLANG-report lines on SYSLOG.\n");
+ printf(" -i: Keep the program foreground without debugging printouts.\n");
+ printf(" -l ...: sets syslog FACILITY code for erlang reports, default: LOG_DAEMON\n");
+ printf(" -d: turn debug printout on, use to verify config file!\n");
+ printf(" twice: prints also interaction with aprs-is system..\n");
+ printf(" -L: Log also all of APRS-IS traffic on relevant log.\n");
+ exit(64); /* EX_USAGE */
+}
+
+static void versionprint()
+{
+ printf("aprx: %s\n", swversion);
+ exit(1);
+}
+
+void fd_nonblockingmode(int fd)
+{
+ int __i = fcntl(fd, F_GETFL, 0);
+ if (__i >= 0) {
+ /* set up non-blocking I/O */
+ __i |= O_NONBLOCK;
+ __i = fcntl(fd, F_SETFL, __i);
+ }
+ // return __i;
+}
+
+int time_reset = 1; // observed time jump, initially as "reset is happening!"
+static struct timeval old_tick; // monotonic
+// static struct timeval old_now; // wall-clock
+
+static int timetick_count;
+
+void timetick(void)
+{
+ ++timetick_count;
+ old_tick = tick;
+ //old_now = now;
+
+ // Monotonic (or as near as possible) clock..
+ // .. which is NOT wall clock time.
+#ifdef HAVE_CLOCK_GETTIME
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ tick.tv_usec = ts.tv_nsec/1000;
+ tick.tv_sec = ts.tv_sec;
+ // if (debug) printf("newtick: %d.%6d\n", tick.tv_sec, tick.tv_usec);
+#else
+ gettimeofday(&tick, NULL); // fallback when no clock_gettime() is available
+#endif
+ // Wall clock time
+ // gettimeofday(&tick, NULL);
+
+ // Main program clears this when appropriate
+ int delta = 0;
+ if (old_tick.tv_sec != 0) {
+ delta = tv_timerdelta_millis(&old_tick, &tick);
+ if (delta < -1) { // Up to 0.99999 seconds backwards for a leap second
+ if (debug) {
+ printf("MONOTONIC TIME JUMPED BACK BY %g SECONDS. ttcallcount=%d\n", delta/1000.0, timetick_count);
+ }
+ time_reset = 1;
+ } else if (delta > 32000) { // 30.0 + leap second + margin
+ if (debug) {
+ printf("MONOTONIC TIME JUMPED FORWARD BY %g SECONDS. ttcallcount=%d mypid=%d\n", delta/1000.0, timetick_count, getpid());
+ }
+ time_reset = 1;
+ } else {
+ // Time is OK.
+ // time_reset = 0;
+ }
+ } else {
+ time_reset = 1;
+ // This happens before argv is parsed, thus debug is never set.
+ // But if it sets happens afterwards...
+ if (debug) printf("Initializing MONOTONIC time\n");
+ }
+ // if (debug>1) printf("TIMETICK %ld:%6d %d delta=%d ms\n", tick.tv_sec, tick.tv_usec, timetick_count, delta);
+}
+
+int main(int argc, char *const argv[])
+{
+ int i;
+ const char *cfgfile = "/etc/aprx.conf";
+ const char *syslog_facility = "NONE";
+ int foreground = 0;
+ int millis;
+ int can_clear_timereset;
+
+ /* Init the poll(2) descriptor array */
+ struct aprxpolls app = APRXPOLLS_INIT;
+
+ timetick(); // init global time references
+
+ setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
+ setvbuf(stderr, NULL, _IOLBF, BUFSIZ);
+
+ while ((i = getopt(argc, argv, "def:hiLl:vV?")) != -1) {
+ switch (i) {
+ case '?':
+ case 'h':
+ usage();
+ break;
+ case 'd':
+ ++debug;
+ ++foreground;
+ break;
+ case 'e':
+ ++erlangout;
+ ++foreground;
+ break;
+ case 'i':
+ ++foreground;
+ break;
+ case 'L':
+ log_aprsis = 1;
+ break;
+ case 'l':
+ syslog_facility = optarg;
+ break;
+ case 'v':
+ ++verbout;
+ ++foreground;
+ break;
+ case 'f':
+ cfgfile = optarg;
+ break;
+ case 'V':
+ versionprint();
+ break;
+ default:
+ break;
+ }
+ }
+
+ interface_init(); // before any interface system and aprsis init !
+ erlang_init(syslog_facility);
+ ttyreader_init();
+#ifdef PF_AX25 /* PF_AX25 exists -- highly likely a Linux system ! */
+ netax25_init();
+#endif
+#ifdef ENABLE_AGWPE
+ agwpe_init();
+#endif
+ dupecheck_init(); // before aprsis_init() !
+#ifndef DISABLE_IGATE
+ aprsis_init();
+#endif
+ filter_init();
+ pbuf_init();
+
+ i = readconfig(cfgfile);
+ if (i) {
+ fflush(stdout);
+ fprintf(stderr, "Seen configuration errors. Aborting!\n");
+ fflush(stderr);
+ exit(1); // CONFIION ERRORS SEEN! ABORT!
+ }
+
+ erlang_start(1);
+#ifndef DISABLE_IGATE
+ historydb_init();
+#endif
+
+ if (debug || verbout) {
+ if (!mycall
+#ifndef DISABLE_IGATE
+ && !aprsis_login
+#endif
+ ) {
+ fflush(stdout);
+ fprintf(stderr,
+ "APRX: NO GLOBAL MYCALL= PARAMETER CONFIGURED, WILL NOT CONNECT APRS-IS\n(This is OK, if no connection to APRS-IS is needed.)\n");
+ } else if (!mycall
+#ifndef DISABLE_IGATE
+ && !aprsis_login
+#endif
+ ) {
+ fflush(stdout);
+ fprintf(stderr,
+ "APRX: NO GLOBAL APRSIS-LOGIN= PARAMETER CONFIGURED, WILL NOT CONNECT APRS-IS\n(This is OK, if no connection to APRS-IS is needed.)\n");
+ }
+ }
+
+ if (!foreground) {
+ /* See if pidfile exists ? */
+ FILE *pf = fopen(pidfile, "r");
+ if (pf) { /* See if the pid exists ? */
+ int rc, er;
+ int pid = -1;
+ fscanf(pf, "%d", &pid);
+ fclose(pf);
+
+ if (pid > 0) {
+ rc = kill(pid, 0);
+ er = errno;
+
+ if ((rc == 0) || (er == EPERM)) {
+ fflush(stdout);
+ fprintf(stderr,
+ "APRX: PIDFILE '%s' EXISTS, AND PROCESSID %d INDICATED THERE EXISTS TOO. FURTHER INSTANCES CAN ONLY BE RUN ON FOREGROUND!\n",
+ pidfile, pid);
+ fflush(stderr);
+ exit(1);
+ }
+ }
+ }
+ }
+
+
+ if (!foreground) {
+ int pid = fork();
+ if (pid > 0) {
+ /* This is parent */
+ exit(0);
+ }
+ /* child and error cases continue on main program.. */
+ poll((void*)&pid, 0, 500);
+
+ }
+
+ if (1) {
+ /* Open the pidfile, if you can.. */
+
+ FILE *pf = fopen(pidfile, "w");
+
+ setsid(); /* Happens or not ... */
+
+ if (!pf) {
+ /* Could not open pidfile! */
+ fflush(stdout);
+ fprintf(stderr, "COULD NOT OPEN PIDFILE: '%s'\n",
+ pidfile);
+ pidfile = NULL;
+ } else {
+ int f = fileno(pf);
+ if (flock(f, LOCK_EX|LOCK_NB) < 0) {
+ if (errno == EWOULDBLOCK) {
+ printf("Could not lock pid file file %s, another process has a lock on it. Another process running - bailing out.\n", pidfile);
+ } else {
+ printf("Failed to lock pid file %s: %s\n", pidfile, strerror(errno));
+ }
+ exit(1);
+ }
+
+ fprintf(pf, "%ld\n", (long) getpid());
+ // Leave it open - flock will prevent double-activation
+ dup(f); // don't care what the fd number is
+ fclose(pf);
+ }
+ }
+
+
+ erlang_start(2); // reset PID, etc..
+
+ // Do following as late as possible..
+
+ // In all cases we close STDIN/FD=0..
+ // .. and replace it with reading from /dev/null..
+ i = open("/dev/null", O_RDONLY, 0);
+ if (i >= 0) { dup2(i, 0); close(i); }
+
+ // Leave STDOUT and STDERR open
+
+ if (!foreground) {
+ // when daemoning, we close also stdout and stderr..
+ dup2(0, 1);
+ dup2(0, 2);
+ }
+
+ // .. but not latter than this.
+
+
+ // Set default signal handling
+
+ signal(SIGTERM, sig_handler);
+ signal(SIGINT, sig_handler);
+ signal(SIGHUP, sig_handler);
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGCHLD, sig_child);
+
+ // Must be after config reading ...
+ netresolv_start();
+#ifndef DISABLE_IGATE
+ aprsis_start();
+#endif
+#ifdef PF_AX25 /* PF_AX25 exists -- highly likely a Linux system ! */
+ netax25_start();
+#endif
+#ifdef ENABLE_AGWPE
+ agwpe_start();
+#endif
+ telemetry_start();
+#ifndef DISABLE_IGATE
+ igate_start();
+#endif
+
+ aprxlog("APRX start");
+
+ // The main loop
+
+ can_clear_timereset = 0;
+
+ while (!die_now) {
+
+ timetick(); // pre-poll
+
+ aprxpolls_reset(&app);
+ tv_timeradd_millis( &app.next_timeout, &tick, 30000 ); // 30 seconds
+
+ i = ttyreader_prepoll(&app);
+ // if (debug>3)printf("after ttyreader prepoll - timeout millis=%d\n",aprxpolls_millis(&app));
+#ifndef DISABLE_IGATE
+ i = aprsis_prepoll(&app);
+ // if (debug>3)printf("after aprsis prepoll - timeout millis=%d\n",aprxpolls_millis(&app));
+#endif
+ i = beacon_prepoll(&app);
+ // if (debug>3)printf("after beacon prepoll - timeout millis=%d\n",aprxpolls_millis(&app));
+#ifdef PF_AX25 /* PF_AX25 exists -- highly likely a Linux system ! */
+ i = netax25_prepoll(&app);
+ // if (debug>3)printf("after netax25 prepoll - timeout millis=%d\n",aprxpolls_millis(&app));
+#endif
+#ifdef ENABLE_AGWPE
+ i = agwpe_prepoll(&app);
+ // if (debug>3)printf("after agwpe prepoll - timeout millis=%d\n",aprxpolls_millis(&app));
+#endif
+ i = erlang_prepoll(&app);
+ // if (debug>3)printf("after erlang prepoll - timeout millis=%d\n",aprxpolls_millis(&app));
+ i = telemetry_prepoll(&app);
+ // if (debug>3)printf("after telemetry prepoll - timeout millis=%d\n",aprxpolls_millis(&app));
+ i = dupecheck_prepoll(&app);
+ // if (debug>3)printf("after dupecheck prepoll - timeout millis=%d\n",aprxpolls_millis(&app));
+ i = digipeater_prepoll(&app);
+ // if (debug>3)printf("after digipeater prepoll - timeout millis=%d\n",aprxpolls_millis(&app));
+#ifndef DISABLE_IGATE
+ i = historydb_prepoll(&app);
+ // if (debug>3)printf("after historydb prepoll - timeout millis=%d\n",aprxpolls_millis(&app));
+ i = dprsgw_prepoll(&app);
+ // if (debug>3)printf("after dprsgw prepoll - timeout millis=%d\n",aprxpolls_millis(&app));
+#endif
+
+ // All pre-polls are done
+ if (can_clear_timereset) {
+ // if (time_reset) {
+ // printf("Clearing time_reset.\n");
+ // }
+ time_reset = 0;
+ } else {
+ can_clear_timereset = 1;
+ }
+
+ // if (app.next_timeout <= now.tv_sec)
+ // app.next_timeout = now.tv_sec + 1; // Just to be on safe side..
+
+ millis = aprxpolls_millis(&app);
+ if (millis < 10)
+ millis = 10;
+
+ i = poll(app.polls, app.pollcount, millis);
+ timetick(); // post-poll
+
+
+ i = beacon_postpoll(&app);
+ i = ttyreader_postpoll(&app);
+#ifdef PF_AX25 /* PF_AX25 exists -- highly likely a Linux system ! */
+ i = netax25_postpoll(&app);
+#endif
+#ifdef ENABLE_AGWPE
+ i = agwpe_postpoll(&app);
+#endif
+#ifndef DISABLE_IGATE
+ i = aprsis_postpoll(&app);
+#endif
+ i = erlang_postpoll(&app);
+ i = telemetry_postpoll(&app);
+ i = dupecheck_postpoll(&app);
+ i = digipeater_postpoll(&app);
+#ifndef DISABLE_IGATE
+ i = historydb_postpoll(&app);
+ i = dprsgw_postpoll(&app);
+#endif
+
+ }
+ aprxpolls_free(&app); // valgrind..
+
+#ifndef DISABLE_IGATE
+ aprsis_stop();
+#endif
+ netresolv_stop();
+
+ if (pidfile) {
+ unlink(pidfile);
+ }
+
+ exit(0);
+}
+
+
+void printtime(char *buf, int buflen)
+{
+ struct timeval tv;
+ struct tm t;
+
+ // Wall lock time for printouts
+ gettimeofday(&tv, NULL);
+ gmtime_r(&tv.tv_sec, &t);
+ // strftime(timebuf, 60, "%Y-%m-%d %H:%M:%S", t);
+ sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d.%03d",
+ t.tm_year+1900,t.tm_mon+1,t.tm_mday,
+ t.tm_hour,t.tm_min,t.tm_sec,
+ (int)(tv.tv_usec / 1000));
+}
+
+static struct syslog_facs {
+ const char *name;
+ int fac_code;
+} syslog_facs[] = {
+ {
+ "NONE", -1}, {
+ "LOG_DAEMON", LOG_DAEMON},
+#ifdef LOG_FTP
+ {
+ "LOG_FTP", LOG_FTP},
+#endif
+#ifdef LOG_LPR
+ {
+ "LOG_LPR", LOG_LPR},
+#endif
+#ifdef LOG_MAIL
+ {
+ "LOG_MAIL", LOG_MAIL},
+#endif
+#ifdef LOG_USER
+ {
+ "LOG_USER", LOG_USER},
+#endif
+#ifdef LOG_UUCP
+ {
+ "LOG_UUCP", LOG_UUCP},
+#endif
+ {
+ "LOG_LOCAL0", LOG_LOCAL0}, {
+ "LOG_LOCAL1", LOG_LOCAL1}, {
+ "LOG_LOCAL2", LOG_LOCAL2}, {
+ "LOG_LOCAL3", LOG_LOCAL3}, {
+ "LOG_LOCAL4", LOG_LOCAL4}, {
+ "LOG_LOCAL5", LOG_LOCAL5}, {
+ "LOG_LOCAL6", LOG_LOCAL6}, {
+ "LOG_LOCAL7", LOG_LOCAL7}, {
+ NULL, 0}
+};
+
+void aprx_syslog_init(const char *syslog_facility_name)
+{
+ static int done_once = 0;
+ int syslog_fac = LOG_DAEMON, i;
+
+ if (done_once) {
+ closelog(); /* We reconfigure from config file! */
+ } else
+ ++done_once;
+ for (i = 0;; ++i) {
+ if (syslog_facs[i].name == NULL) {
+ fprintf(stderr,
+ "Sorry, unknown erlang syslog facility code name: %s, not supported in this system.\n",
+ syslog_facility_name);
+ fprintf(stderr, "Accepted list is:");
+ for (i = 0;; ++i) {
+ if (syslog_facs[i].name == NULL)
+ break;
+ fprintf(stderr, " %s",
+ syslog_facs[i].name);
+ }
+ fprintf(stderr, "\n");
+ break;
+ }
+ if (strcasecmp(syslog_facs[i].name, syslog_facility_name)
+ == 0) {
+ syslog_fac = syslog_facs[i].fac_code;
+ break;
+ }
+ }
+
+ if (syslog_fac >= 0) {
+ erlangsyslog = 1;
+ openlog("aprx", LOG_NDELAY | LOG_PID, syslog_fac);
+ }
+}
+
+#ifdef HAVE_STDARG_H
+#ifdef __STDC__
+void aprxlog(const char *fmt, ...)
+#else
+void aprxlog(fmt)
+#endif
+#else
+/* VARARGS */
+void aprxlog(va_list)
+va_dcl
+#endif
+{
+ va_list ap;
+ char timebuf[60];
+
+ printtime(timebuf, sizeof(timebuf));
+ if (verbout) {
+#ifdef HAVE_STDARG_H
+ va_start(ap, fmt);
+#else
+ const char *fmt;
+ va_start(ap);
+ fmt = va_arg(ap, const char *);
+#endif
+
+ fprintf(stdout, "%s ", timebuf);
+ vfprintf(stdout, fmt, ap);
+ (void)fprintf(stdout, "\n");
+
+#ifdef HAVE_STDARG_H
+ va_end(ap);
+#endif
+ }
+
+ if (aprxlogfile) {
+ FILE *fp;
+
+#ifdef HAVE_STDARG_H
+ va_start(ap, fmt);
+#else
+ const char *fmt;
+ va_start(ap);
+ fmt = va_arg(ap, const char *);
+#endif
+
+
+#if defined(HAVE_PTHREAD_CREATE) && defined(ENABLE_PTHREAD)
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+#endif
+ fp = fopen(aprxlogfile, "a");
+ if (fp != NULL) {
+ setlinebuf(fp);
+ fprintf(fp, "%s ", timebuf);
+ vfprintf(fp, fmt, ap);
+ (void)fprintf(fp, "\n");
+ fclose(fp);
+ }
+#if defined(HAVE_PTHREAD_CREATE) && defined(ENABLE_PTHREAD)
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+#endif
+
+#ifdef HAVE_STDARG_H
+ va_end(ap);
+#endif
+ }
+}
+
diff --git a/aprx.conf.in b/aprx.conf.in
new file mode 100644
index 0000000..ae78413
--- /dev/null
+++ b/aprx.conf.in
@@ -0,0 +1,390 @@
+#
+# Simple sample configuration file for the APRX-2 -- an APRS iGate and Digipeater
+#
+# This configuration is structured with Apache HTTPD style tags
+# which then contain subsystem parameters.
+#
+
+#
+# For simple case, you need to adjust 4 things:
+# - Mycall parameter
+# - passcode parameter in APRS-IS configuration
+# - Select correct type of interface (ax25-device or serial-device)
+# - Optionally set a beacon telling where this system is
+# - Optionally enable digipeater with or without tx-igate
+#
+
+#
+#
+# Define the parameters in following order:
+# 1) <aprsis> ** zero or one
+# 2) <logging> ** zero or one
+# 3) <interface> ** there can be multiple!
+# 4) <beacon> ** zero to many
+# 5) <telemetry> ** zero to many
+# 6) <digipeater> ** zero to many (at most one for each Tx)
+#
+
+#
+# Global macro for simplified callsign definition:
+# Usable for 99+% of cases.
+#
+
+mycall N0CALL-1
+
+#
+# Global macro for simplified "my location" definition in
+# place of explicit "lat nn lon mm" at beacons. Will also
+# give "my location" reference for "filter m/100".
+#
+#myloc lat ddmm.mmN lon dddmm.mmE
+
+<aprsis>
+# The aprsis login parameter:
+# Station callsignSSID used for relaying APRS frames into APRS-IS.
+# Use this only to define other callsign for APRS\-IS login.
+#
+#login OTHERCALL-7 # login defaults to $mycall
+
+#
+# Passcode for your callsign:
+# Unique code for your callsign to allow transmitting packets
+# into the APRS-IS.
+#
+passcode -1
+
+# APRS-IS server name and optional portnumber.
+#
+# WARNING: Do not change from default port number [14580]
+# unless you are absolutely certain you want
+# something else, and you allow that something
+# else also affect your tx-igate behaviour!
+#
+server rotate.aprs2.net
+#server euro.aprs2.net
+#server asia.aprs2.net
+#server noam.aprs2.net
+#server soam.aprs2.net
+#server aunz.aprs2.net
+
+# Some APRS-IS servers tell every about 20 seconds to all contact
+# ports that they are there and alive. Others are just silent.
+# Default value is 3*"heartbeat" + some --> 120 (seconds)
+#
+#heartbeat-timeout 0 # Disabler of heartbeat timeout
+
+# APRS-IS server may support some filter commands.
+# See: http://www.aprs-is.net/javAPRSFilter.aspx
+#
+# You can define the filter as single long quoted string, or as
+# many short segments with explaining comments following them.
+#
+# Usability of these filters for a Tx-iGate is dubious, but
+# they exist in case you for example want to Tx-iGate packets
+# from some source callsigns in all cases even when they are
+# not in your local area.
+#
+#filter "possibly multiple filter specs in quotes"
+#
+#filter "m/100" # My-Range filter: positions within 100 km from my location
+#filter "f/OH2XYZ-3/50" # Friend-Range filter: 50 km of friend's last beacon position
+</aprsis>
+
+<logging>
+
+# pidfile is UNIX way to tell that others that this program is
+# running with given process-id number. This has compiled-in
+# default value of: pidfile @VARRUN@/aprx.pid
+#
+pidfile @VARRUN@/aprx.pid
+
+
+# rflog defines a rotatable file into which all RF-received packets
+# are logged. The host system can rotate it at any time without
+# need to signal the aprx that the file has been moved.
+#
+rflog @VARLOG@/aprx-rf.log
+
+# aprxlog defines a rotatable file into which most important
+# events on APRS-IS connection are logged, namely connects and
+# disconnects. The host system can rotate it at any time without
+# need to signal the aprx that the file has been moved.
+#
+aprxlog @VARLOG@/aprx.log
+
+# dprslog defines a rotatable file into which most important
+# events on DPRS receiver gateways are logged.
+# The host system can rotate it at any time without need to
+# signal the aprx that the file has been moved.
+#
+#dprslog @VARLOG@/dprs.log
+
+# erlangfile defines a mmap():able binary file, which stores
+# running sums of interfaces upon which the channel erlang
+# estimator runs, and collects data.
+# Depending on the system, it may be running on a filesystem
+# that actually retains data over reboots, or it may not.
+# With this backing store, the system does not loose cumulating
+# erlang data over the current period, if the restart is quick,
+# and does not stradle any exact minute.
+# (Do restarts at 15 seconds over an even minute..)
+# This file is around 0.7 MB per each interface talking APRS.
+# If this file is not defined and it can not be created,
+# internal non-persistent in-memory storage will be used.
+#
+# Built-in default value is: @VARRUN@/aprx.state
+#
+#erlangfile @VARRUN@/aprx.state
+
+</logging>
+
+
+# *********** Multiple <interface> definitions can follow *********
+
+# ax25-device Lists AX.25 ports by their callsigns that in Linux
+# systems receive APRS packets. If none are defined,
+# or the system is not Linux, the AX.25 network receiver
+# is not enabled. Used technologies need at least
+# Linux kernel 2.4.x
+#
+# tx-ok Boolean telling if this device is able to transmit.
+#
+
+#<interface>
+# ax25-device $mycall
+# #tx-ok false # transmitter enable defaults to false
+# #telem-to-is true # set to 'false' to disable
+#</interface>
+
+
+#
+# The TNC serial options. Parameters are:
+# - /dev/ttyUSB1 -- tty device
+# - 19200 -- baud rate, supported ones are:
+# 1200, 2400, 4800, 9600, 19200, 38400
+# - 8n1 -- 8-bits, no parity, one stop-bit,
+# no other supported modes
+# - "KISS" - plain basic KISS mode
+# - "XORSUM" alias "BPQCRC" - KISS with BPQ "CRC" byte
+# - "SMACK" alias "CRC16" - KISS with real CRC
+# - "FLEXNET" - KISS with real CRC
+# - "TNC2" - TNC2 monitor format
+# - "DPRS" - DPRS (RX) GW
+#
+
+#<interface>
+# serial-device /dev/ttyUSB0 19200 8n1 KISS
+# #callsign $mycall # callsign defaults to $mycall
+# #tx-ok false # transmitter enable defaults to false
+# #telem-to-is true # set to 'false' to disable
+#</interface>
+
+#<interface>
+# serial-device /dev/ttyUSB1 19200 8n1 TNC2
+# #callsign $mycall # callsign defaults to $mycall
+# #tx-ok false # TNC2 monitor can not have transmitter
+# #telem-to-is true # set to 'false' to disable
+#</interface>
+
+#<interface>
+# serial-device /dev/ttyUSB1 19200 8n1 DPRS
+# callsign dprsgwcallsign # must define actual callsign
+# #tx-ok false # DPRS monitor can not do transmit
+# #telem-to-is true # set to 'false' to disable
+#</interface>
+
+
+# *********** Multiple <beacon> definitions can follow *********
+<beacon>
+#
+# Beacons are sent out to radio transmitters AND/OR APRSIS.
+# Default is "both", other modes are settable.
+#
+#beaconmode { aprsis | both | radio }
+#
+# Beacons are sent from a circullar transmission queue, total cycle time
+# of that queue is 20 minutes by default, and beacons are "evenly"
+# distributed along it. Actual intervals are randomized to be anything
+# in between 80% and 100% of the cycle-size / number-of-beacons.
+# First beacon is sent out 30 seconds after system start.
+# Tune the cycle-size to be suitable to your number of defined beacons.
+#
+#cycle-size 20m
+#
+# Basic beaconed thing is positional message of type "!":
+#
+#beacon symbol "R&" lat "0000.00N" lon "00000.00E" comment "Rx-only iGate"
+#beacon symbol "R&" $myloc comment "Rx-only iGate"
+#
+#Following are basic options:
+# 'symbol' no default, must be defined!
+# 'lat' coordinate latitude: ddmm.mmN (no default!)
+# 'lon' coordinate longitude: dddmm.mmE (no default!)
+# '$myloc' coordinate values taken from global 'myloc' entry,
+# and usable in place of explicit 'lat'+'lon'.
+# 'comment' optional tail part of the item, default is nothing
+#
+# Sample symbols:
+# R& is for "Rx-only iGate"
+# I& is for "Tx-iGate"
+# /# is for "Digipeater"
+# I# is for "Tx-iGate + Digipeater""
+#
+#Additional options are:
+# 'srccall' parameter sets claimed origination address.
+# 'dstcall' sets destination address, default "APRXnn"
+# 'interface' parameter picks an interface (must be "tx-ok true" type)
+# 'via' sets radio distribution pattern, default: none.
+# 'timefix' On APRS messages with HMS timestamp (hour:min:sec), the
+# system fixes appropriate field with transmit time timestamp.
+#
+# Message type is by default '!', which is positional no timestamp format.
+# Other possible formats are definable with options:
+# 'type' Single character setting type: ! = / @, default: !
+# 'item' Defines a name of Item (')') type beacons.
+# 'object' Defines a name of Object (';') type beacons.
+#
+# 'file' option tells a file at which a _raw_ APRS message content is
+# expected to be found as first line of text. Line ending newline
+# is removed, and no escapes are supported. The timefix is
+# available, though probably should not be used.
+# No \-processing is done on read text line.
+#
+# 'exec' option tells a computer program which returns to stdout _raw_ APRS
+# message content without newline. The timefix is
+# available, though probably should not be used.
+# No \-processing is done on read text line.
+#
+# The parameter sets can vary:
+# a) 'srccall nnn-n dstcall "string" symbol "R&" lat "ddmm.mmN" lon "dddmm.mmE" [comment "any text"]
+# b) 'srccall nnn-n dstcall "string" symbol "R&" $myloc [comment "any text"]
+# c) 'srccall nnn-n dstcall "string" raw "string"'
+#
+# The a) form flags on some of possible syntax errors in parameters.
+# It will also create only "!" type messages. The dest parameter
+# defaults to "APRS", but can be used to give other destinations.
+# The via parameter can be used to add other keywords, like "NOGATE".
+#
+# Writing correct RAW format beacon message is very hard,
+# which is evidenced by the frequency of bad syntax texts
+# people so often put there... If you can not be persuaded
+# not to do it, then at least VERIFY the beacon result on
+# web service like findu.com, or aprs.fi
+#
+# Do remember that the \ -character has special treatment in the
+# Aprx configuration parser. If you want a '\' on APRS content,
+# then you encode it on configuration file as: '\\'
+#
+# Stranger combinations with explicite "transmit this to interface X":
+#
+#beacon file /tmp/wxbeacon.txt
+#beacon interface N0CALL-3 srccall N0CALL-3 \
+# raw "!0000.00NR00000.00E&Rx-only iGate"
+#beacon interface N0CALL-3 srccall N0CALL-3 \
+# raw "!0000.00NI00000.00E&Tx-iGate"
+#beacon interface $mycall symbol "R&" $myloc \
+# comment "Rx-only iGate"
+#beacon interface $mycall symbol "I&" $myloc \
+# comment "Tx-iGate"
+#beacon exec /usr/bin/telemetry.pl
+#beacon timeout 20 exec /usr/bin/telemetry.pl
+#beacon interface N0CALL-3 srccall N0CALL-3 \
+# timeout 20 exec /usr/bin/telemetry.pl
+#
+</beacon>
+
+# *********** <telemetry> definition(s) follow *********
+#
+# The system will always send telemetry for all of its interfaces
+# to APRSIS, but there is an option to define telemetry to be sent
+# to radio channel by using following sections for each transmitter
+# that is wanted to send out the telemetry.
+#
+# transmitter - callsign referring to <interface>
+# via - optional via-path, only 1 callsign!
+# source - one or more of <interface> callsigns for which
+# the telemetry transmission is wanted for
+#
+#<telemetry>
+# transmitter $mycall
+# via TRACE1-1
+# source $mycall
+#</telemetry>
+
+# *********** <digipeater> definition(s) follow *********
+#
+# The digipeater definitions tell transmitters that receive
+# AX.25 packets from possibly multiple sources, and then what
+# to do on the AX.25 headers of those messages.
+#
+# There is one transmitter per digipeater -- and inversely, there
+# can be at most one digipeater for each transmitter.
+#
+# In each digipeater there is at least one <source>, usually same
+# as the transmitter. You may use same <source> on multiple
+# <digipeater>s. Using multiple instances of same <source> on
+# a single <digipeater> does not crash the system, but it can cause
+# packet duplication in case of non-APRS protocols (like AX.25 CONS)
+#
+# Use only at most two levels of viscous-delay in your <digipeater>.
+# Immediate sending is by "0", and a delayed sending is any value
+# from 1 to 9. This system does not correctly support other than
+# immediate sending and one level of delay.
+#
+# Note: In order to igate correct when multiple receivers and
+# transmitters are used on single channel, the <interface>
+# definitions of each radio port must have associated
+# "igate-group N" parameter which has N of value 1 to 3.
+# See the aprx-manual.pdf for details.
+# (Default software compilation allows you to have up to
+# three channels of APRS operation.)
+#
+#<digipeater>
+# transmitter $mycall
+# #ratelimit 60 120 # default: average 60 packets/minute,
+# # # burst max 120 packets/minute
+# #srcratelimit 10 20 # Example: by sourcecall:
+# # average 10 packets/minute,
+# # burst max 20 packets/minute
+#
+# <source>
+# source $mycall
+# # #relay-type digipeated # default mode is "digipeated"
+# # viscous-delay 0 # no viscous delay for RF->RF digipeating
+# # ratelimit 60 120 # default: average 60 packets/minute,
+# # # burst max 120 packets/minute
+# ## filter a/la/lo/la/lo # service area filter
+# ## filter -b/CALL # always block these
+# </source>
+#
+# # Diversity receiver which combines to the primary
+# # Tx/Rx transmitter. There can be as many of these
+# # as you can connect on this machine.
+# #<source>
+# # source RXPORT-1
+# # #relay-type digipeated # default mode is "digipeated"
+# # viscous-delay 0 # no viscous delay for RF->RF digipeating
+# # ratelimit 60 120 # default: average 60 packets/minute,
+# # # burst max 120 packets/minute
+# ## filter a/la/lo/la/lo # service area filter
+# ## filter -b/CALL # always block these
+# </source>
+#
+# #<source> # APRSIS source adds a TX-IGATE behaviour
+# # source APRSIS
+# # relay-type third-party # Must define this for APRSIS source!
+# # viscous-delay 5 # Recommendation: 5 seconds delay to give
+# # # RF delivery time make itself known.
+# # ratelimit 60 120 # default: average 60 packets/minute,
+# # # burst max 120 packets/minute
+# ## filter a/la/lo/la/lo # service area filter
+# ## filter -b/CALL # always block these
+# #</source>
+#
+# #<source> # DPRS source adds a DPRS->APRS RF gate
+# # interface DPRS
+# # ratelimit 60 120 # default: average 60 packets/minute,
+# # # burst max 120 packets/minute
+# # relay-type third-party # Must define this for DPRS source!
+# #</source>
+#</digipeater>
diff --git a/aprx.h b/aprx.h
new file mode 100644
index 0000000..b48ff34
--- /dev/null
+++ b/aprx.h
@@ -0,0 +1,784 @@
+/* **************************************************************** *
+ * *
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ * **************************************************************** */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <assert.h>
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+#ifdef HAVE_TIME_H
+# include <time.h>
+#endif
+#include <unistd.h>
+#ifdef HAVE_STDLIB_H
+# include <stdlib.h>
+#endif
+#define __need_size_t
+#define __need_NULL
+#ifdef HAVE_STDDEF_H
+# include <stddef.h>
+#endif
+#ifdef HAVE_STDARG_H
+#include <stdarg.h>
+#else
+#include <varargs.h>
+#endif
+
+#if defined(HAVE_PTHREAD_CREATE) && defined(ENABLE_PTHREAD)
+#include <pthread.h>
+pthread_t aprsis_thread;
+pthread_attr_t pthr_attrs;
+#endif
+
+#ifdef _FOR_VALGRIND_
+#define strdup aprx_strdup
+#define strcmp aprx_strcmp
+#define strncmp aprx_strncmp
+#define memcmp aprx_memcmp
+#define memcpy aprx_memcpy
+#define memchr aprx_memchr
+#define memrchr aprx_memrchr
+#define strlen aprx_strlen
+#define strcpy aprx_strcpy
+#define strncpy aprx_strncpy
+#define strchr aprx_strchr
+
+// Single char at the time naive implementations for valgrind runs
+extern int memcmp(const void *p1, const void *p2, size_t n);
+extern void *memcpy(void *dest, const void *src, size_t n);
+extern size_t strlen(const char *p);
+extern char *strdup(const char *s);
+extern int strcmp(const char *s1, const char *s2);
+extern int strncmp(const char *s1, const char *s2, size_t n);
+extern char *strcpy(char *dest, const char *src);
+extern char *strncpy(char *dest, const char *src, size_t n);
+extern void *memchr(const void *s, int c, size_t n);
+extern char *strchr(const char *s, int c);
+
+// extern declarators for standard functions
+extern void *memset(void *s, int c, size_t n);
+extern char *strerror(const int n);
+extern void *memmove(void *dest, const void *src, size_t n);
+extern char *strtok(char *str, const char *delim);
+extern int strcasecmp(const char *s1, const char *s2);
+
+#else
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif
+#endif
+
+extern void *memrchr(const void *s, int c, size_t n);
+
+#include <termios.h>
+#include <errno.h>
+#include <syslog.h>
+#include <regex.h>
+#ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+#endif
+
+#include <ctype.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <math.h>
+
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in.h>
+
+/* Radio interface groups on igate receiption history tracking.
+ * Value range: 1 to MAX_IF_GROUP-1.
+ * Value 0 is reserved for APRSIS.
+ */
+#define MAX_IF_GROUP 4
+
+#define CALLSIGNLEN_MAX 9
+
+struct aprxpolls; // forward declarator
+
+#include "cellmalloc.h"
+#include "historydb.h"
+#include "keyhash.h"
+#include "pbuf.h"
+
+#if 0
+#define static /*ignore statics during debug */
+#endif
+
+struct aprx_interface; // Forward declarator
+
+
+struct configfile {
+ const char *name;
+ FILE *fp;
+ int linenum_i; // internal linenum
+ int linenum; // externally presented, first line of folded multilines
+ char buf[8010];
+};
+
+/* aprxpolls.c */
+struct aprxpolls {
+ struct pollfd *polls;
+ int pollcount;
+ int pollsize;
+ struct timeval next_timeout;
+};
+#define APRXPOLLS_INIT { NULL, 0, 0, {0,0} }
+
+extern int aprxpolls_millis(struct aprxpolls *app);
+extern void aprxpolls_reset(struct aprxpolls *app);
+extern struct pollfd *aprxpolls_new(struct aprxpolls *app);
+extern void aprxpolls_free(struct aprxpolls *app);
+
+/* aprx.c */
+#ifndef DISABLE_IGATE
+extern const char *aprsis_login;
+#endif
+extern int die_now;
+extern const char *mycall;
+extern const char *tocall;
+extern const uint8_t tocall25[7];
+extern float myloc_lat;
+extern float myloc_coslat;
+extern float myloc_lon;
+extern const char *myloc_latstr;
+extern const char *myloc_lonstr;
+
+extern void fd_nonblockingmode(int fd);
+
+extern const char *swname;
+extern const char *swversion;
+
+extern void timetick(void);
+extern struct timeval now; // Public wall lock time that can jump around
+extern struct timeval tick; // Monotonic clock, progresses regularly from boot. NOT wall clock time.
+extern int time_reset; // Set during ONE call cycle of prepolls
+extern int debug;
+extern int verbout;
+extern int erlangout;
+extern const char *rflogfile;
+extern const char *aprxlogfile;
+extern const char *dprslogfile;
+extern const char *erlanglogfile;
+extern const char *pidfile;
+
+extern void printtime(char *buf, int buflen);
+extern void aprx_syslog_init(const char *syslog_fac);
+
+#ifdef HAVE_STDARG_H
+#ifdef __STDC__
+extern void aprxlog(const char *fmt, ...);
+#endif
+#else
+/* VARARGS */
+extern void aprxlog(va_list);
+#endif
+
+/* netresolver.c */
+extern void netresolv_start(void); // separate thread working on this!
+extern void netresolv_stop(void);
+
+struct netresolver {
+ char const *hostname;
+ char const *port;
+ time_t re_resolve_time;
+ struct addrinfo ai;
+ struct sockaddr sa;
+};
+
+extern struct netresolver *netresolv_add(const char *hostname, const char *port);
+
+/* ttyreader.c */
+typedef enum {
+ LINETYPE_KISS, /* all KISS variants without CRC on line */
+ LINETYPE_KISSSMACK, /* KISS/SMACK variants with CRC on line */
+ LINETYPE_KISSFLEXNET, /* KISS/FLEXNET with CRC on line */
+ LINETYPE_KISSBPQCRC, /* BPQCRC - really XOR sum of data bytes,
+ also "AEACRC" */
+ LINETYPE_TNC2, /* text line from TNC2 in monitor mode */
+ LINETYPE_AEA, /* not implemented... */
+
+ LINETYPE_DPRSGW /* Special DPRS RX GW mode */
+} LineType;
+
+typedef enum {
+ KISSSTATE_SYNCHUNT = 0,
+ KISSSTATE_COLLECTING,
+ KISSSTATE_KISSFESC
+} KissState;
+
+struct serialport {
+ int fd; /* UNIX fd of the port */
+
+ struct timeval wait_until;
+ time_t last_read_something; /* Used by serial port functionality
+ watchdog */
+ int read_timeout; /* seconds */
+ int poll_millis; /* milliseconds (0 = none.) */
+
+ LineType linetype;
+
+ KissState kissstate; /* state for KISS frame reader,
+ also for line collector */
+
+ /* NOTE: The smack_probe is separate on all
+ ** sub-tnc:s on SMACK loop
+ */
+ time_t smack_probe[8]; /* if need to send SMACK probe, use this
+ to limit their transmit frequency. */
+ int smack_subids; /* bitset; 0..7; could use char... */
+
+
+ struct termios tio; /* tcsetattr(fd, TCSAFLUSH, &tio) */
+ /* stty speed 19200 sane clocal pass8 min 1 time 5 -hupcl ignbrk -echo -ixon -ixoff -icanon */
+
+ const char *ttyname; /* "/dev/ttyUSB1234-bar22-xyz7" --
+ Linux TTY-names can be long.. */
+ const char *ttycallsign[16]; /* callsign */
+ const void *netax25[16];
+
+ char *initstring[16]; /* optional init-string to be sent to
+ the TNC, NULL OK */
+ int initlen[16]; /* .. as it can have even NUL-bytes,
+ length is important! */
+
+ struct aprx_interface *interface[16];
+
+
+ uint8_t rdbuf[2000]; /* buffering area for raw stream read */
+ int rdlen, rdcursor; /* rdlen = last byte in buffer,
+ rdcursor = next to read.
+ When rdlen == 0, buffer is empty. */
+
+ time_t rdline_time; /* last time something was added there */
+ uint8_t rdline[2000]; /* processed into lines/records */
+ int rdlinelen; /* length of this record */
+
+ uint8_t wrbuf[4000]; /* buffering area for raw stream read */
+ int wrlen, wrcursor; /* wrlen = last byte in buffer,
+ wrcursor = next to write.
+ When wrlen == 0, buffer is empty. */
+
+ void *dprsgw; /* opaque DPRS GW data */
+};
+
+
+extern int ttyreader_prepoll(struct aprxpolls *);
+extern int ttyreader_postpoll(struct aprxpolls *);
+extern void ttyreader_init(void);
+// Old style init: ttyreader_serialcfg()
+extern const char *ttyreader_serialcfg(struct configfile *cf, char *param1, char *str);
+// New style init: ttyreader_new()
+extern struct serialport *ttyreader_new(void);
+extern void ttyreader_register(struct serialport *tty);
+extern int ttyreader_getc(struct serialport *tty);
+// extern void ttyreader_setlineparam(struct serialport *tty, const char *ttyname, const int baud, int const kisstype);
+// extern void ttyreader_setkissparams(struct serialport *tty, const int tncid, const char *callsign, const int timeout);
+extern int ttyreader_parse_ttyparams(struct configfile *cf, struct serialport *tty, char *str);
+extern void ttyreader_linewrite(struct serialport *S);
+extern int ttyreader_parse_nullparams(struct configfile *cf, struct serialport *tty, char *str);
+
+extern void hexdumpfp(FILE *fp, const uint8_t *buf, const int len, int axaddr);
+extern void aprx_cfmakeraw(struct termios *, int f);
+
+extern void tv_timerbounds(const char *, struct timeval *tv, const int margin, void (*resetfunc)(void*), void *resetarg );
+extern void tv_timeradd_millis(struct timeval *res, struct timeval * const a, const int millis);
+extern void tv_timeradd_seconds(struct timeval *res, struct timeval * const a, const int seconds);
+extern int tv_timerdelta_millis(struct timeval * const _now, struct timeval * const _target);
+extern int tv_timercmp(struct timeval * const a, struct timeval * const b);
+extern int timecmp(time_t a, time_t b);
+
+
+/* ax25.c */
+extern int ax25_to_tnc2_fmtaddress(char *dest, const uint8_t *src,
+ int markflag);
+extern int ax25_to_tnc2(const struct aprx_interface *aif, const char *portname,
+ const int tncid, const int cmdbyte,
+ const uint8_t *frame, const int framelen);
+extern void ax25_filter_add(const char *p1, const char *p2);
+extern int ax25_format_to_tnc(const uint8_t *frame, const int framelen,
+ char *tnc2buf, const int tnc2buflen,
+ int *frameaddrlen, int *tnc2addrlen,
+ int *is_aprs, int *ui_pid);
+extern int parse_ax25addr(uint8_t ax25[7], const char *text,
+ int ssidflags);
+
+
+#ifndef DISABLE_IGATE
+/* aprsis.c */
+extern int aprsis_add_server(const char *server, const char *port);
+extern int aprsis_set_heartbeat_timeout(const int tout);
+extern int aprsis_set_filter(const char *filter);
+extern int aprsis_set_login(const char *login);
+#define qTYPE_IGATED 'R'
+#define qTYPE_LOCALGEN 'S'
+extern int aprsis_queue(const char *addr, int addrlen,
+ const char qtype, const char *gwcall,
+ const char *text, int textlen);
+extern int aprsis_prepoll(struct aprxpolls *app);
+extern int aprsis_postpoll(struct aprxpolls *app);
+extern void aprsis_init(void);
+extern void aprsis_start(void);
+extern void aprsis_stop(void);
+extern int aprsis_config(struct configfile *cf);
+extern char * const aprsis_loginid;
+#endif
+
+/* beacon.c */
+extern int beacon_prepoll(struct aprxpolls *app);
+extern int beacon_postpoll(struct aprxpolls *app);
+extern int beacon_config(struct configfile *cf);
+extern void beacon_childexit(int pid);
+
+/* config.c */
+extern void *readconfigline(struct configfile *cf);
+extern int configline_is_comment(struct configfile *cf);
+extern int readconfig(const char *cfgfile);
+extern char *config_SKIPSPACE(char *Y);
+extern char *config_SKIPTEXT(char *Y, int *lenp);
+extern void config_STRLOWER(char *Y);
+extern void config_STRUPPER(char *Y);
+extern int validate_callsign_input(char *callsign, int strict); // this modifies callsign string!
+extern int config_parse_interval(const char *par, int *resultp);
+extern int config_parse_boolean(const char *par, int *resultp);
+extern const char *scan_int(const char *p, int len, int*val, int*seen_space);
+extern int validate_degmin_input(const char *s, int maxdeg);
+
+
+/* dprsgw.c */
+extern int dprsgw_pulldprs(struct serialport *S);
+extern int dprsgw_prepoll(struct aprxpolls *app);
+extern int dprsgw_postpoll(struct aprxpolls *app);
+
+
+/* erlang.c */
+extern void erlang_init(const char *syslog_facility_name);
+extern void erlang_start(int do_create);
+extern int erlang_prepoll(struct aprxpolls *app);
+extern int erlang_postpoll(struct aprxpolls *app);
+
+/* igate.c */
+#ifndef DISABLE_IGATE
+extern void igate_start(void);
+extern void igate_from_aprsis(const char *ax25, int ax25len);
+extern void igate_to_aprsis(const char *portname, const int tncid, const char *tnc2buf, int tnc2addrlen, int tnc2len, const int discard, const int strictax25);
+extern void enable_tx_igate(const char *, const char *);
+#endif
+extern void rflog(const char *portname, char direction, int discard, const char *tnc2buf, int tnc2len);
+extern const char *tnc2_verify_callsign_format(const char *t, int starok, int strictax25, const char *e);
+
+/* netax25.c */
+#ifdef PF_AX25 /* PF_AX25 exists -- highly likely a Linux system ! */
+extern void netax25_init(void);
+extern void netax25_start(void);
+extern const void* netax25_open(const char *ifcallsign);
+extern int netax25_prepoll(struct aprxpolls *);
+extern int netax25_postpoll(struct aprxpolls *);
+extern void * netax25_addrxport(const char *callsign, const struct aprx_interface *aif);
+extern void netax25_sendax25(const void *nax25, const void *ax25, int ax25len);
+extern void netax25_sendto(const void *nax25, const uint8_t *axaddr, const int axaddrlen, const char *axdata, const int axdatalen);
+#endif
+
+/* telemetry.c */
+
+#define USE_ONE_MINUTE_DATA 0
+
+extern void telemetry_start(void);
+extern int telemetry_prepoll(struct aprxpolls *app);
+extern int telemetry_postpoll(struct aprxpolls *app);
+extern int telemetry_config(struct configfile *cf);
+
+
+typedef enum {
+ ERLANG_RX,
+ ERLANG_DROP,
+ ERLANG_TX
+} ErlangMode;
+
+extern void erlang_add(const char *portname, ErlangMode erl, int bytes, int packets);
+extern void erlang_set(const char *portname, int bytes_per_minute);
+
+extern int erlangsyslog;
+extern int erlanglog1min;
+extern const char *erlang_backingstore;
+
+/* The struct erlangline is shared in between the aprx, and
+ erlang reporter application: aprx-stat */
+
+struct erlang_rxtxbytepkt {
+ long packets_rx, packets_rxdrop, packets_tx ;
+ long bytes_rx, bytes_rxdrop, bytes_tx ;
+ time_t update;
+};
+
+
+struct erlangline {
+ const void *refp;
+ int index;
+ char name[31];
+ uint8_t __subport;
+ time_t last_update;
+
+ int erlang_capa; /* bytes, 1 minute */
+
+ struct erlang_rxtxbytepkt SNMP; /* SNMPish counters */
+
+#ifdef ERLANGSTORAGE
+ struct erlang_rxtxbytepkt erl1m; /* 1 minute erlang period */
+ struct erlang_rxtxbytepkt erl10m; /* 10 minute erlang period */
+ struct erlang_rxtxbytepkt erl60m; /* 60 minute erlang period */
+#else
+#if (USE_ONE_MINUTE_DATA == 1)
+ struct erlang_rxtxbytepkt erl1m; /* 1 minute erlang period */
+#else
+ struct erlang_rxtxbytepkt erl10m; /* 10 minute erlang period */
+#endif
+#endif
+
+#ifdef ERLANGSTORAGE
+ int e1_cursor, e1_max; /* next store point + max cursor index */
+ int e10_cursor, e10_max;
+ int e60_cursor, e60_max;
+#else
+#if (USE_ONE_MINUTE_DATA == 1)
+ int e1_cursor, e1_max; /* next store point + max cursor index */
+#else
+ int e10_cursor, e10_max;
+#endif
+#endif
+
+#ifdef ERLANGSTORAGE
+
+#define APRXERL_1M_COUNT (60*24) // 1 day of 1 minute data
+#define APRXERL_10M_COUNT (60*24*7) // 1 week of 10 minute data
+#define APRXERL_60M_COUNT (24*31*3) // 3 months of hourly data
+ struct erlang_rxtxbytepkt e1[APRXERL_1M_COUNT];
+ struct erlang_rxtxbytepkt e10[APRXERL_10M_COUNT];
+ struct erlang_rxtxbytepkt e60[APRXERL_60M_COUNT];
+#else /* EMBEDDED */ /* When making very small memory footprint,
+ like embedding on Linksys WRT54GL ... */
+
+#define APRXERL_1M_COUNT (22) // 22 minutes of 1 minute data
+#define APRXERL_10M_COUNT (3) // 30 minutes of 10 minute data
+#if (USE_ONE_MINUTE_DATA == 1)
+ struct erlang_rxtxbytepkt e1[APRXERL_1M_COUNT];
+#else
+ struct erlang_rxtxbytepkt e10[APRXERL_10M_COUNT];
+#endif
+#endif
+};
+
+struct erlanghead {
+ char title[32];
+ int version; /* format version */
+ int linecount;
+ time_t last_update;
+
+ pid_t server_pid;
+ time_t start_time;
+
+ char mycall[16];
+
+ double align_filler;
+};
+
+#define ERLANGLINE_STRUCT_VERSION ((sizeof(struct erlanghead)<<16)+sizeof(struct erlangline))
+
+extern struct erlanghead *ErlangHead;
+extern struct erlangline **ErlangLines;
+extern int ErlangLinesCount;
+
+
+/* dupecheck.c */
+
+
+typedef struct dupe_record_t {
+ struct dupe_record_t *next;
+ uint32_t hash;
+ time_t t; // creation time
+ time_t t_exp; // expiration time
+
+ struct pbuf_t *pbuf; // To send packet out of delayed processing,
+ // this pointer must be non-NULL.
+ int16_t seen; // Count of times this packet has been seen
+ // on non-delayed processing. First one will
+ // be sent when pbuf is != NULL.
+ int16_t delayed_seen; // Count of times this packet has been seen
+ // on delayed processing. The packet may get
+ // sent, if "seen" count is zero at delay end.
+ int16_t seen_on_transmitter; // Source of where it was seen is same
+ // as this digipeater transmitter.
+ int16_t refcount; // number of references on this entry
+
+ int16_t alen; // Address length
+ int16_t plen; // Payload length
+
+ char addresses[20];
+ char *packet;
+ char packetbuf[200]; /* 99.9+ % of time this is enough.. */
+} dupe_record_t;
+
+#define DUPECHECK_DB_SIZE 16 /* Hash index table size - per dupechecker */
+
+typedef struct dupecheck_t {
+ int storetime;
+ struct dupe_record_t *dupecheck_db[DUPECHECK_DB_SIZE]; /* Hash index table */
+} dupecheck_t;
+
+extern void dupecheck_init(void); /* Inits the dupechecker subsystem */
+extern dupecheck_t *dupecheck_new(const int storetime); /* Makes a new dupechecker */
+extern dupe_record_t *dupecheck_get(dupe_record_t *dp); // increment refcount
+extern void dupecheck_put(dupe_record_t *dp); // decrement refcount
+extern dupe_record_t *dupecheck_aprs(dupecheck_t *dp, const char *addr, const int alen, const char *data, const int dlen); /* aprs checker */
+extern dupe_record_t *dupecheck_pbuf(dupecheck_t *dp, struct pbuf_t *pb, const int viscous_delay); /* pbuf checker */
+extern int dupecheck_prepoll(struct aprxpolls *app);
+extern int dupecheck_postpoll(struct aprxpolls *app);
+
+
+/* crc.c */
+
+// kissencoder() needs direct access to CRC tables..
+extern const uint16_t crc16_table[256];
+extern const uint16_t crc_flex_table[256];
+
+extern uint16_t calc_crc_16(const uint8_t *buf, int n); /* SMACK's CRC-16 */
+extern uint16_t calc_crc_flex(const uint8_t *buf, int n); /* FLEXNET's CRC */
+extern uint16_t calc_crc_ccitt(uint16_t crc, const uint8_t *buf, int len); // X.25's FCS a.k.a. CRC-CCITT a.k.a. CCITT-CRC
+extern int check_crc_16(const uint8_t *buf, int n); /* SMACK's CRC-16 */
+extern int check_crc_flex(const uint8_t *buf, int n); /* FLEXNET's CRC */
+extern int check_crc_ccitt(const uint8_t *buf, int n);
+
+/* KISS protocol encoder/decoder specials */
+
+#define KISS_FEND (0xC0)
+#define KISS_FESC (0xDB)
+#define KISS_TFEND (0xDC)
+#define KISS_TFESC (0xDD)
+
+extern int kissencoder(void *, int, LineType, const void *, int, int);
+extern void kiss_kisswrite(struct serialport *S, const int tncid, const uint8_t *ax25raw, const int ax25rawlen);
+extern int kiss_pullkiss(struct serialport *S);
+extern void kiss_poll(struct serialport *S);
+
+
+/* digipeater.c */
+typedef enum {
+ DIGIRELAY_UNSET,
+ DIGIRELAY_DIGIPEAT,
+ DIGIRELAY_DIGIPEAT_DIRECTONLY,
+ DIGIRELAY_THIRDPARTY
+} digi_relaytype;
+
+struct filter_t; // Forward declarator
+struct digipeater; // Forward declarator
+
+struct tracewide {
+ int maxreq;
+ int maxdone;
+ int is_trace;
+
+ int nkeys;
+ char **keys;
+ int *keylens;
+};
+
+struct digipeater_source {
+ struct digipeater *parent;
+ digi_relaytype src_relaytype;
+ struct aprx_interface *src_if;
+ struct filter_t *src_filters;
+ struct tracewide *src_trace;
+ struct tracewide *src_wide;
+#ifndef DISABLE_IGATE
+ char *via_path; // for APRSIS only
+ char *msg_path; // for APRSIS only
+ uint8_t ax25viapath[7]; // APRSIS
+ uint8_t msgviapath[7]; // APRSIS
+#endif
+
+ float tokenbucket;
+ float tbf_increment;
+ float tbf_limit;
+
+ // Viscous queue is at <source>, but used dupechecker
+ // is <digipeater> -wide, common to all sources in that
+ // digipeater.
+ int viscous_delay;
+ int viscous_queue_size;
+ int viscous_queue_space;
+ struct dupe_record_t **viscous_queue;
+
+ int sourceregscount;
+ regex_t **sourceregs;
+
+ int destinationregscount;
+ regex_t **destinationregs;
+
+ int viaregscount;
+ regex_t **viaregs;
+
+ int dataregscount;
+ regex_t **dataregs;
+};
+
+struct digipeater {
+ struct aprx_interface *transmitter;
+ float tokenbucket; // Per transmitter TokenBucket filter
+ float tbf_increment;
+ float tbf_limit;
+ float src_tbf_increment; // Source call specific TokenBucket rules
+ float src_tbf_limit;
+
+ dupecheck_t *dupechecker; // Per transmitter dupecheck
+#ifndef DISABLE_IGATE
+ historydb_t *historydb; // Per transmitter HistoryDB
+#endif
+
+ const struct tracewide *trace;
+ const struct tracewide *wide;
+
+ int sourcecount;
+ struct digipeater_source **sources;
+};
+
+extern int digipeater_prepoll(struct aprxpolls *app);
+extern int digipeater_postpoll(struct aprxpolls *app);
+extern int digipeater_config(struct configfile *cf);
+extern void digipeater_receive(struct digipeater_source *src, struct pbuf_t *pb);
+extern int digipeater_receive_filter(struct digipeater_source *src, struct pbuf_t *pb);
+extern dupecheck_t *digipeater_find_dupecheck(const struct aprx_interface *aif);
+
+/* interface.c */
+
+typedef enum {
+ IFTYPE_UNSET,
+ IFTYPE_AX25,
+ IFTYPE_SERIAL,
+ IFTYPE_TCPIP,
+ IFTYPE_AGWPE,
+ IFTYPE_NULL,
+ IFTYPE_APRSIS
+} iftype_e;
+
+
+struct aprx_interface {
+ iftype_e iftype;
+ int timeout;
+ int16_t ifindex; // Absolute index on this interface
+ int16_t ifgroup; // Group definition on this interface
+
+ char *callsign; // Callsign of this interface
+ uint8_t ax25call[7]; // AX.25 address field format callsign
+
+ int aliascount;
+ char **aliases; // Alias callsigns for this interface
+
+ int8_t subif; // Sub-interface index - for KISS uses
+ uint8_t txrefcount; // Number of digipeaters using this as Tx
+ unsigned tx_ok:1; // This is Tx interface
+ unsigned telemeter_to_is:1; // Telemeter this to APRS-IS
+ unsigned telemeter_to_rf:1; // Telemeter this to this radio port
+ unsigned telemeter_newformat:1; // Telemeter in "new format"
+
+ int initlength;
+ char *initstring;
+
+ const void *nax25p; // used on IFTYPE_AX25
+#ifdef ENABLE_AGWPE
+ const void *agwpe; // used on IFTYPE_AGWPE
+#endif
+ struct serialport *tty; // used on IFTYPE_SERIAL, IFTYPE_TCPIP
+
+ int digisourcecount;
+ struct digipeater_source **digisources;
+};
+
+extern struct aprx_interface aprsis_interface;
+
+extern int top_interfaces_group;
+extern int all_interfaces_count;
+extern struct aprx_interface **all_interfaces;
+
+extern void interface_init(void);
+extern int interface_config(struct configfile *cf);
+extern struct aprx_interface *find_interface_by_callsign(const char *callsign);
+
+extern int interface_is_beaconable( const struct aprx_interface *iface );
+extern int interface_is_telemetrable(const struct aprx_interface *iface );
+
+extern void interface_receive_ax25( const struct aprx_interface *aif, const char *ifaddress, const int is_aprs, const int ui_pid, const uint8_t *axbuf, const int axaddrlen, const int axlen, const char *tnc2buf, const int tnc2addrlen, const int tnc2len);
+extern void interface_transmit_ax25(const struct aprx_interface *aif, uint8_t *axaddr, const int axaddrlen, const char *axdata, const int axdatalen);
+extern void interface_receive_3rdparty(const struct aprx_interface *aif, const char *fromcall, const char *origtocall, const char *gwtype, const char *tnc2data, const int tnc2datalen);
+extern int interface_transmit_beacon(const struct aprx_interface *aif, const char *src, const char *dest, const char *via, const char *tncbuf, const int tnclen);
+extern int process_message_to_myself(const struct aprx_interface*const srcif, const struct pbuf_t*const pb);
+
+
+/* pbuf.c */
+extern void pbuf_init(void);
+extern struct pbuf_t *pbuf_get(struct pbuf_t *pb);
+extern void pbuf_put(struct pbuf_t *pb);
+extern struct pbuf_t *pbuf_new(const int is_aprs, const int digi_like_aprs, const int tnc2addrlen, const char *tnc2buf, const int tnc2len, const int ax25addrlen, const void *ax25buf, const int ax25len );
+
+
+/* parse_aprs.c */
+extern int parse_aprs(struct pbuf_t*const pb, historydb_t*const historydb);
+
+struct aprs_message_t {
+ const char *body; /* message body */
+ const char *msgid;
+
+ int body_len;
+ int msgid_len;
+ int is_ack;
+ int is_rej;
+};
+
+extern int parse_aprs_message(const struct pbuf_t*const pb, struct aprs_message_t*const am);
+
+
+/* filter.c */
+struct filter_t; // Forward declarator
+struct client_t; // Forward declarator
+struct worker_t; // Forward declarator
+
+extern void filter_init(void);
+extern int filter_parse(struct filter_t **ffp, const char *filt);
+extern void filter_free(struct filter_t *c);
+extern int filter_process(struct pbuf_t *pb, struct filter_t *f, historydb_t *historydb);
+
+extern void filter_preprocess_dupefilter(struct pbuf_t *pb);
+extern void filter_postprocess_dupefilter(struct pbuf_t *pb, historydb_t *historydb);
+
+extern float filter_lat2rad(float lat);
+extern float filter_lon2rad(float lon);
+
+#ifdef ENABLE_AGWPE
+/* agwpesocket.c */
+extern void *agwpe_addport(const char *hostname, const char *hostport, const char *agwpeport, const struct aprx_interface *interface);
+extern void agwpe_sendto(const void *_ap, const uint8_t *axaddr, const int axaddrlen, const char *axdata, const int axdatalen);
+
+extern int agwpe_prepoll(struct aprxpolls *);
+extern int agwpe_postpoll(struct aprxpolls *);
+extern void agwpe_init(void);
+extern void agwpe_start(void);
+#endif
diff --git a/aprx.spec b/aprx.spec
new file mode 100644
index 0000000..f620b8e
--- /dev/null
+++ b/aprx.spec
@@ -0,0 +1,118 @@
+Name: aprx
+Version: 2.08.svn593
+Release: 1%{?dist}
+Summary: Hamradio APRS iGate / Digipeater
+License: BSD
+URL: http://ham.zmailer.org/oh2mqk/aprx/
+Source0: http://ham.zmailer.org/oh2mqk/aprx/%{name}-%{version}.tar.gz
+
+
+%if 0%{?rhel} >= 7 || 0%{?fedora} >= 16
+BuildRequires: systemd-units
+%endif
+
+%if 0%{?fedora} >= 18
+Requires(post): systemd
+Requires(preun): systemd
+Requires(postun): systemd
+
+%post
+%systemd_post %{name}.service
+
+%preun
+%systemd_preun %{name}.service
+
+%postun
+%systemd_postun_with_restart %{name}.service
+
+%else 0%{?fedora} = 17
+Requires(post): systemd-units
+Requires(preun): systemd-units
+Requires(postun): systemd-units
+
+%post
+if [ $1 -eq 1 ] ; then
+ # Initial installation
+ /bin/systemctl daemon-reload >/dev/null 2>&1 || :
+fi
+
+%preun
+if [ $1 -eq 0 ] ; then
+ # Package removal, not upgrade
+ /bin/systemctl --no-reload disable aprx.service > /dev/null 2>&1 || :
+ /bin/systemctl stop aprx.service > /dev/null 2>&1 || :
+fi
+
+%postun
+/bin/systemctl daemon-reload >/dev/null 2>&1 || :
+if [ $1 -ge 1 ] ; then
+ # Package upgrade, not uninstall
+ /bin/systemctl try-restart aprx.service >/dev/null 2>&1 || :
+fi
+
+%endif
+
+%description
+Aprx is an APRS iGate that has minimal system requirements. It can handle
+an arbitrary number of radio modems, optionally relay APRS packets from radio
+to the APRS-IS network, optionally digipeat AX25 with or without NEWn-N
+rules, optionally relay APRS packets from APRS-IS to radio (TX-iGate)
+
+%prep
+%setup -q
+
+%build
+%configure --with-erlangstorage CFLAGS="-m32 -march=i386" LDFLAGS="-m32 -march=i386"
+make %{?_smp_mflags}
+make logrotate.aprx
+
+%install
+mkdir -p $RPM_BUILD_ROOT/etc/sysconfig
+mkdir -p $RPM_BUILD_ROOT/etc/logrotate.d
+mkdir -p $RPM_BUILD_ROOT/var/log/aprx
+
+make install DESTDIR=$RPM_BUILD_ROOT
+
+install -m 644 logrotate.aprx $RPM_BUILD_ROOT%{_sysconfdir}/logrotate.d/aprx
+install -m 644 rpm/aprx.default $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig/aprx
+
+%if 0%{?rhel} >= 7 || 0%{?fedora} >= 16
+mkdir -p $RPM_BUILD_ROOT%{_unitdir}
+install -m 644 rpm/aprx.service $RPM_BUILD_ROOT%{_unitdir}/%{name}.service
+%else
+mkdir -p $RPM_BUILD_ROOT%{_initddir}
+install -m 755 rpm/aprx.init $RPM_BUILD_ROOT%{_initddir}/aprx
+%endif
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%files
+%defattr(-,root,root,-)
+# INSTALL not bundled
+%doc LICENSE README TODO PROTOCOLS
+%doc ChangeLog
+%doc aprx.conf aprx-complex.conf
+%doc doc/aprx-manual.pdf
+%doc ViscousDigipeater.README ViscousDigipeaterTxEffect.png
+%dir /var/log/aprx
+%if 0%{?rhel} >= 7 || 0%{?fedora} >= 16
+%{_unitdir}/%{name}.service
+%else
+%{_initddir}/%{name}
+%endif
+%config(noreplace) %{_sysconfdir}/aprx.conf
+%config(noreplace) %{_sysconfdir}/sysconfig/aprx
+%config(noreplace) %{_sysconfdir}/logrotate.d/aprx
+%{_sbindir}/aprx
+%{_sbindir}/aprx-stat
+%doc %{_mandir}/man8/aprx.8.gz
+%doc %{_mandir}/man8/aprx-stat.8.gz
+
+
+%changelog
+* Thu Oct 11 2012 Andrew Elwell <Andrew.Elwell at gmail.com> - 2.08.svn593
+- Packaging for Fedora
+
+* Sat Jan 12 2008 Matti Aarnio - OH2MQK - KP20NG <oh2mqk at sral.fi> -
+- RPM framework added
diff --git a/aprxpolls.c b/aprxpolls.c
new file mode 100644
index 0000000..bb73032
--- /dev/null
+++ b/aprxpolls.c
@@ -0,0 +1,51 @@
+/* **************************************************************** *
+ * *
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ * **************************************************************** */
+
+
+#include "aprx.h"
+
+
+/* aprxpolls libary functions.. */
+
+
+void aprxpolls_reset(struct aprxpolls *app)
+{
+ app->pollcount = 0;
+}
+
+int aprxpolls_millis(struct aprxpolls *app)
+{
+ return tv_timerdelta_millis(&tick,&app->next_timeout);
+}
+
+struct pollfd *aprxpolls_new(struct aprxpolls *app)
+{
+ struct pollfd *p;
+ app->pollcount += 1;
+ if (app->pollcount >= app->pollsize) {
+ app->pollsize += 8;
+ app->polls = realloc(app->polls,
+ sizeof(struct pollfd) * app->pollsize);
+ // valgrind polishing..
+ p = &(app->polls[app->pollcount - 1]);
+ memset(p, 0, sizeof(struct pollfd) * 8);
+ }
+
+ assert(app->polls);
+
+ p = &(app->polls[app->pollcount - 1]);
+ memset(p, 0, sizeof(struct pollfd));
+ return p;
+}
+
+void aprxpolls_free(struct aprxpolls *app) {
+ free(app->polls);
+ app->polls = NULL;
+}
diff --git a/ax25.c b/ax25.c
new file mode 100644
index 0000000..2d94605
--- /dev/null
+++ b/ax25.c
@@ -0,0 +1,295 @@
+/* **************************************************************** *
+ * *
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ * **************************************************************** */
+
+
+#include "aprx.h"
+
+/*
+ * --
+ * C0 00
+ * 82 A0 B4 9A 88 A4 60
+ * 9E 90 64 90 A0 9C 72
+ * 9E 90 64 A4 88 A6 E0
+ * A4 8C 9E 9C 98 B2 61
+ * 03 F0
+ * 21 36 30 32 39 2E 35 30 4E 2F 30 32 35 30 35 2E 34 33 45 3E 20 47 43 53 2D 38 30 31 20
+ * C0
+ * --
+ */
+
+int ax25_to_tnc2_fmtaddress(char *dest, const uint8_t *src, int markflag)
+{
+ int i, c;
+ int ssid;
+ int seen_space = 0;
+
+ /* 6 bytes of station callsigns in shifted ASCII format.. */
+ for (i = 0; i < 6; ++i, ++src) {
+ c = (*src) & 0xFF;
+ if (c & 1) {
+ *dest = 0;
+ return ~c; /* Bad address-end flag ? */
+ }
+
+ /* Don't copy spaces or 0 bytes */
+ c = c >> 1;
+ if (c == 0 || c == 0x20) {
+ seen_space = 1;
+ continue;
+ }
+ if (!seen_space &&
+ (('A' <= c && c <= 'Z') ||
+ ('0' <= c && c <= '9'))) {
+ *dest++ = c;
+ } else {
+ *dest = 0;
+ return ~c; // Bad character in callsign
+ }
+ }
+ /* 7th byte carries SSID et.al. bits */
+ c = (*src) & 0xFF;
+ /* (c & 1) can be non-zero - at last address! */
+
+ ssid = (c >> 1) & 0x0F;
+ if (ssid) { /* don't print SSID==0 value */
+ dest += sprintf(dest, "-%d", ssid);
+ }
+
+ if ((c & 0x80) && markflag) {
+ *dest++ = '*'; /* Has been digipeated.. */
+ }
+ *dest = 0;
+
+ return c;
+}
+
+// Return 0 on OK, != 0 on errors
+int parse_ax25addr(uint8_t ax25[7], const char *text, int ssidflags)
+{
+ int i = 0;
+ int ssid = 0;
+ char c;
+
+ while (i < 6) {
+ c = *text;
+
+ if (c == '-' || c == '*' || c == '\0')
+ break;
+ if (!(('A' <= c && c <= 'Z') || ('0' <= c && c <= '9'))) {
+ // Valid chars: [A-Z0-9]
+ return 1;
+ }
+
+ ax25[i] = c << 1;
+
+ ++text;
+ ++i;
+ }
+
+ while (i < 6) {
+ ax25[i] = ' ' << 1; // they are wanted as spaces..
+ ++i;
+ }
+
+ ax25[6] = ssidflags;
+ if (*text == 0) return 0;
+
+ if (*text == '-') {
+ ++text;
+ } else if ( *text != '*' && *text != 0) {
+ return 1;
+ }
+
+ for (; (*text != '\0') && (*text != '*') &&
+ ('0' <= *text) && (*text <= '9'); ++text) {
+
+ ssid = ssid * 10 + (*text - '0');
+ }
+
+ if (*text == '*') {
+ ++text;
+ ssidflags |= 0x80; // Set H-bit..
+ ax25[6] |= 0x80; // Set H-bit..
+ }
+
+ if (ssid > 15 || *text != '\0') {
+ return 1; // Bad values
+ }
+ ssid &= 0x0F; // Limit it to 4 bits
+
+ ax25[6] = (ssid << 1) | ssidflags;
+
+ return 0;
+}
+
+int ax25_format_to_tnc(const uint8_t *frame, const int framelen,
+ char *tnc2buf, const int tnc2buflen,
+ int *frameaddrlen, int *tnc2addrlen,
+ int *is_aprs, int *ui_pid)
+{
+ int i, j;
+ const uint8_t *s = frame;
+ const uint8_t *e = frame + framelen;
+ char *t = tnc2buf;
+ int viacount = 0;
+
+ if (debug>1) {
+ printf("ax25_format_to_tnc() len=%d ",framelen);
+ hexdumpfp(stdout, frame, framelen, 1);
+ printf("\n");
+ }
+
+ if (framelen > sizeof(tnc2buf) - 80) {
+ /* Too much ! Too much! */
+ return 0;
+ }
+
+
+ /* Phase 1: scan address fields. */
+ /* Source and Destination addresses must be printed in altered order.. */
+
+
+ *t = 0;
+ i = ax25_to_tnc2_fmtaddress(t, frame + 7, 0); /* source */
+ t += strlen(t);
+ *t++ = '>';
+ *t = 0; // end-string, just in case..
+
+ j = ax25_to_tnc2_fmtaddress(t, frame + 0, 0); /* destination */
+ t += strlen(t);
+
+// if (!((i & 0xE0) == 0x60 && (j & 0xE0) == 0xE0)) {
+// if (debug) printf("Ax25FmtToTNC2: %s SSID-bytes: %02x,%02x\n", tnc2buf, i,j);
+// }
+
+ if (i < 0 /* || ((i & 0xE0) != 0x60)*/) { // Top 3 bits should be: 011
+ /* Bad format */
+ if (debug)
+ printf("Ax25FmtToTNC2: Bad source address; SSID-byte=0x%02x\n",i);
+ return 0;
+ }
+ if (j < 0/* || ((j & 0xE0) != 0xE0)*/) { // Top 3 bits should be: 111
+ /* Bad format */
+ if (debug)
+ printf("Ax25FmtToTNC2: Bad destination address; SSID-byte=0x%x\n",j);
+ return 0;
+ }
+
+
+ s = frame + 14;
+
+ if ((i & 1) == 0) { /* addresses continue after the source! */
+
+ for (; s < e;) {
+ *t++ = ','; /* separator char */
+ *t = 0; // end-string, just in case..
+ i = ax25_to_tnc2_fmtaddress(t, s, 1); // Top 3 bits are: H11 ( H = "has been digipeated" )
+ if (i < 0 /* || ((i & 0x60) != 0x60) */) {
+ /* Bad format */
+ if (debug) printf("Ax25FmtToTNC2: Bad via address; addr='%s' SSID-byte=0x%x\n",t,i);
+ return 0;
+ }
+
+ t += strlen(t);
+ s += 7;
+ ++ viacount;
+ if (i & 1)
+ break; /* last address */
+ }
+ }
+ if (viacount > 8) {
+ if (debug)
+ printf("Ax25FmtToTNC2: Found %d via fields, limit is 8!\n", viacount);
+ return 0;
+ }
+
+ *frameaddrlen = s - frame;
+ *tnc2addrlen = t - tnc2buf;
+
+ /* Address completed */
+
+ if ((s + 2) >= e) // too short payload
+ return 0; /* never happens ?? */
+
+ *t++ = ':'; /* end of address */
+ *t = 0; // end-string, just in case..
+
+ if (s[0] != 0x03) {
+ // Not AX.25 UI frame
+ *ui_pid = -1;
+ return t - tnc2buf;
+ /* But say that the frame is OK, and
+ let it be possibly copied to Linux
+ internal AX.25 network. */
+ }
+ if (s[0] == 0x03 && s[1] != 0xF0) {
+ // AX.25 UI frame, but no with APRS's PID value
+ *ui_pid = s[1];
+ return t - tnc2buf;
+ }
+
+ s += 2; // Skip over Control and PID bytes
+ *ui_pid = 0xF0; // This was previously verified
+
+ /* Copy payload - stop at first LF char */
+ for (; s < e; ++s) {
+ if (*s == '\n') /* Stop at first LF */
+ break;
+ *t++ = *s;
+ }
+ *t = 0;
+
+ /* Chop off possible immediately trailing CR characters */
+ for ( ;t > tnc2buf; --t ) {
+ int c = t[-1];
+ if (c != '\r') {
+ break;
+ }
+ t[-1] = 0;
+ }
+
+ *is_aprs = 1;
+ return t - tnc2buf;
+}
+
+/* Convert the binary packet to TNC2 monitor text format.
+ Return 0 if conversion fails (format errors), 1 when format is OK. */
+
+int ax25_to_tnc2(const struct aprx_interface *aif, const char *portname,
+ const int tncid, const int cmdbyte,
+ const uint8_t *frame, const int framelen)
+{
+ int frameaddrlen = 0;
+
+ char tnc2buf[2800];
+ int tnc2len = 0, tnc2addrlen = 0, is_aprs = 0, ui_pid = 0;
+
+ tnc2len = ax25_format_to_tnc( frame, framelen,
+ tnc2buf, sizeof(tnc2buf),
+ & frameaddrlen, &tnc2addrlen,
+ & is_aprs, &ui_pid );
+
+ if (tnc2len == 0) return 0; // Bad parse result
+
+ // APRS type packets are first rx-igated (and rflog()ed)
+#ifndef DISABLE_IGATE
+ if (is_aprs) {
+ igate_to_aprsis(portname, tncid, tnc2buf, tnc2addrlen, tnc2len, 0, 1);
+ }
+#endif
+
+ // Send to interface system to receive it.. (digipeater!)
+ // A noop if the interface is actually NULL.
+ interface_receive_ax25(aif, portname, is_aprs, ui_pid,
+ frame, frameaddrlen, framelen,
+ tnc2buf, tnc2addrlen, tnc2len);
+
+ return 1;
+}
diff --git a/beacon.c b/beacon.c
new file mode 100644
index 0000000..d0c588b
--- /dev/null
+++ b/beacon.c
@@ -0,0 +1,1235 @@
+/* **************************************************************** *
+ * *
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ * **************************************************************** */
+
+#include "aprx.h"
+
+struct beaconmsg {
+ time_t nexttime;
+ int interval;
+ const struct aprx_interface *interface;
+ const char *src;
+ const char *dest;
+ const char *via;
+ const char *msg;
+ const char *filename;
+ const char *execfile;
+ int8_t beaconmode; // -1: net only, 0: both, +1: radio only
+ int8_t timefix;
+ int timeout;
+};
+
+struct beaconset {
+ struct beaconmsg **beacon_msgs;
+
+ struct timeval beacon_nexttime;
+ float beacon_cycle_size;
+
+ int beacon_msgs_count;
+ int beacon_msgs_cursor;
+
+ int exec_pid;
+ int exec_fd;
+ time_t exec_deadline; // seconds
+ char *exec_buf;
+ int exec_buf_length;
+ int exec_buf_space;
+ struct beaconmsg *exec_bm;
+};
+
+static struct beaconset **bsets;
+static int bsets_count;
+
+static void beacon_it(struct beaconset *bset, struct beaconmsg *bm);
+
+
+static void beacon_reset(struct beaconset *bset)
+{
+ tv_timeradd_seconds(&bset->beacon_nexttime, &tick, 30); // start 30 seconds from now
+ bset->beacon_msgs_cursor = 0;
+}
+
+static void beacon_set(struct configfile *cf,
+ const char *p1,
+ char *str,
+ const int beaconmode,
+ struct beaconset *bset)
+{
+ const char *srcaddr = NULL;
+ const char *destaddr = NULL;
+ const char *via = NULL;
+ const char *name = NULL;
+ int buflen = strlen(p1) + strlen(str ? str : "") + 10;
+ char *buf = alloca(buflen);
+ const char *to = NULL;
+ char *code = NULL;
+ const char *lat = NULL;
+ const char *lon = NULL;
+ char *comment = NULL;
+ char *type = NULL;
+ const struct aprx_interface *aif = NULL;
+ int has_fault = 0;
+
+ struct beaconmsg *bm = calloc(1, sizeof(*bm));
+
+ *buf = 0;
+
+ if (debug) {
+ printf("BEACON parameters: ");
+ }
+
+ while (*p1) {
+
+ /* if (debug)
+ printf("p1='%s' ",p1); */
+
+ if (strcmp(p1, "interface") == 0 ||
+ strcmp(p1, "to") == 0) {
+
+ if (to != NULL) {
+ has_fault = 1;
+ printf("%s:%d ERROR: Double definition of %s parameter\n",
+ cf->name, cf->linenum, p1);
+ }
+
+ to = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ if (beaconmode < 0) {
+ printf("%s:%d ERROR: beaconmode APRSIS is incompatible with beaconing to designated interface ('%s %s')\n",
+ cf->name, cf->linenum, p1, to);
+ has_fault = 1;
+ goto discard_bm; // sigh..
+ }
+
+ if (strcasecmp(to,"$mycall") == 0) {
+ to = mycall;
+ } else {
+ config_STRUPPER((void*)to);
+ }
+
+ aif = find_interface_by_callsign(to);
+ if ((aif != NULL) && (!aif->tx_ok)) {
+ aif = NULL; // Not an TX interface :-(
+ if (debug)printf("\n");
+ printf("%s:%d ERROR: beacon interface '%s' that is not a TX capable interface.\n",
+ cf->name, cf->linenum, to);
+ has_fault = 1;
+ goto discard_bm; // sigh..
+ } else if (aif == NULL) {
+ if (debug)printf("\n");
+ printf("%s:%d ERROR: beacon interface '%s' that is not a known interface.\n",
+ cf->name, cf->linenum, to);
+ has_fault = 1;
+ }
+
+ if (debug)
+ printf("interface '%s' ", to);
+
+ } else if (strcmp(p1, "srccall") == 0 ||
+ strcmp(p1, "for") == 0) {
+
+ if (srcaddr != NULL) {
+ has_fault = 1;
+ printf("%s:%d ERROR: Double definition of %s parameter\n",
+ cf->name, cf->linenum, p1);
+ }
+ srcaddr = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ if (strcasecmp(srcaddr,"$mycall") == 0) {
+ srcaddr = mycall;
+ } else {
+ config_STRUPPER((void*)srcaddr);
+ }
+
+ // What about ITEM and OBJECT ?
+
+ // if (!validate_callsign_input((char *) srcaddr),1) {
+ // if (debug)printf("\n");
+ // printf("Invalid rfbeacon FOR callsign");
+ // }
+
+ if (debug)
+ printf("srccall '%s' ", srcaddr);
+
+ } else if (strcmp(p1, "dstcall") == 0 ||
+ strcmp(p1, "dest") == 0) {
+
+ if (destaddr != NULL) {
+ has_fault = 1;
+ printf("%s:%d ERROR: Double definition of %s parameter\n",
+ cf->name, cf->linenum, p1);
+ }
+ destaddr = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ config_STRUPPER((void*)destaddr);
+
+ if (debug)
+ printf("dstcall '%s' ", destaddr);
+
+ } else if (strcmp(p1, "via") == 0) {
+
+ if (via != NULL) {
+ has_fault = 1;
+ printf("%s:%d ERROR: Double definition of %s parameter\n",
+ cf->name, cf->linenum, p1);
+ }
+ via = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ config_STRUPPER((void*)via);
+
+ if (debug)
+ printf("via '%s' ", via);
+
+ } else if (strcmp(p1, "name") == 0) {
+
+ if (name != NULL) {
+ has_fault = 1;
+ printf("%s:%d ERROR: Double definition of %s parameter\n",
+ cf->name, cf->linenum, p1);
+ }
+ name = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ if (debug)
+ printf("name '%s' ", name);
+
+ } else if (strcmp(p1, "item") == 0) {
+ if (name != NULL) {
+ has_fault = 1;
+ printf("%s:%d ERROR: Double definition of %s parameter\n",
+ cf->name, cf->linenum, p1);
+ }
+ if (type != NULL) {
+ has_fault = 1;
+ printf("%s:%d ERROR: Double definition of type parameter\n",
+ cf->name, cf->linenum);
+ }
+ type = ")";
+ name = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ if (debug)
+ printf("item '%s' ", name);
+
+ } else if (strcmp(p1, "object") == 0) {
+ if (name != NULL) {
+ has_fault = 1;
+ printf("%s:%d ERROR: Double definition of %s parameter\n",
+ cf->name, cf->linenum, p1);
+ }
+ if (type != NULL) {
+ has_fault = 1;
+ printf("%s:%d ERROR: Double definition of type parameter\n",
+ cf->name, cf->linenum);
+ }
+ type = ";";
+
+ name = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ if (debug)
+ printf("object '%s' ", name);
+
+ } else if (strcmp(p1, "type") == 0) {
+ /* text up to .. 40 chars */
+
+ if (type != NULL) {
+ has_fault = 1;
+ printf("%s:%d ERROR: Double definition of %s parameter\n",
+ cf->name, cf->linenum, p1);
+ }
+ type = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+ type = strdup(type);
+
+ if (debug)
+ printf("type '%s' ", type);
+ if (type[1] != 0 || (type[0] != '!' &&
+ type[0] != '=' &&
+ type[0] != '/' &&
+ type[0] != '@' &&
+ type[0] != ';' &&
+ type[0] != ')')) {
+ has_fault = 1;
+ printf("%s:%d Sorry, packet constructor's supported APRS packet types are only: ! = / @ ; )\n",
+ cf->name, cf->linenum);
+ }
+
+ } else if (strcmp(p1, "$myloc") == 0) {
+ if (myloc_latstr != NULL) {
+ lat = myloc_latstr;
+ lon = myloc_lonstr;
+ } else {
+ has_fault = 1;
+ printf("%s:%d ERROR: $myloc has not been defined.\n",
+ cf->name, cf->linenum);
+
+ }
+ } else if (strcmp(p1, "lat") == 0) {
+ /* ddmm.mmN */
+
+ if (lat != NULL) {
+ has_fault = 1;
+ printf("%s:%d ERROR: Double definition of %s parameter\n",
+ cf->name, cf->linenum, p1);
+ }
+ lat = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ if (!has_fault && validate_degmin_input(lat, 90)) {
+ has_fault = 1;
+ printf("%s:%d ERROR: Latitude input has bad format: '%s'\n",
+ cf->name, cf->linenum, lat);
+ }
+
+ if (debug)
+ printf("lat '%s' ", lat);
+
+ } else if (strcmp(p1, "lon") == 0) {
+ /* dddmm.mmE */
+
+ if (lon != NULL) {
+ has_fault = 1;
+ printf("%s:%d ERROR: Double definition of %s parameter\n",
+ cf->name, cf->linenum, p1);
+ }
+ lon = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ if (validate_degmin_input(lon, 180)) {
+ has_fault = 1;
+ printf("Longitude input has bad format: '%s'\n",lon);
+ }
+
+ if (debug)
+ printf("lon '%s' ", lon);
+
+ } else if (strcmp(p1, "symbol") == 0) {
+ /* R& */
+
+ if (code != NULL) {
+ has_fault = 1;
+ printf("%s:%d ERROR: Double definition of %s parameter\n",
+ cf->name, cf->linenum, p1);
+ }
+ code = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+ if (strlen(code) != 2) {
+ has_fault = 1;
+ printf("Symbol code lenth is not exactly 2 chars\n");
+ }
+
+ if (debug)
+ printf("symbol '%s' ", code);
+
+ } else if (strcmp(p1, "comment") == 0) {
+ /* text up to .. 40 chars */
+
+ if (comment != NULL) {
+ has_fault = 1;
+ printf("%s:%d ERROR: Double definition of %s parameter\n",
+ cf->name, cf->linenum, p1);
+ }
+ comment = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ if (debug)
+ printf("comment '%s' ", comment);
+
+ } else if (strcmp(p1, "raw") == 0) {
+
+ p1 = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ if (bm->msg != NULL) {
+ has_fault = 1;
+ printf("%s:%d ERROR: Double definition of %s parameter\n",
+ cf->name, cf->linenum, p1);
+ } else
+ bm->msg = strdup(p1);
+
+ // FIXME: validate the data with APRS parser...
+
+ if (debug)
+ printf("raw '%s' ", bm->msg);
+
+ } else if (strcmp(p1, "file") == 0) {
+
+ p1 = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ if (bm->filename != NULL) {
+ has_fault = 1;
+ printf("%s:%d ERROR: Double definition of %s parameter\n",
+ cf->name, cf->linenum, p1);
+ } else
+ bm->filename = strdup(p1);
+
+ if (debug)
+ printf("file '%s' ", bm->filename);
+
+ } else if (strcmp(p1, "exec") == 0) {
+
+ p1 = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ if (bm->execfile != NULL) {
+ has_fault = 1;
+ printf("%s:%d ERROR: Double definition of %s parameter\n",
+ cf->name, cf->linenum, p1);
+ } else
+ bm->execfile = strdup(p1);
+
+ // Set default timeout if not yet set.
+ if (bm->timeout == 0)
+ bm->timeout = 10;
+
+ if (debug)
+ printf("exec file '%s' ", bm->execfile);
+
+ } else if (strcmp(p1, "timeout") == 0) {
+
+ p1 = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ bm->timeout = atoi(p1);
+
+ if (debug)
+ printf("timeout %d ", bm->timeout);
+
+ } else if (strcmp(p1, "timefix") == 0) {
+ if (bm->timefix) {
+ has_fault = 1;
+ printf("%s:%d ERROR: Double definition of %s parameter\n",
+ cf->name, cf->linenum, p1);
+ }
+ bm->timefix = 1;
+ if (debug)
+ printf("timefix ");
+
+ } else {
+
+ has_fault = 1;
+#if 0
+ if (debug)
+ printf("Unknown keyword: '%s'", p1);
+
+ p1 = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+#else
+ /* Unknown keyword, a raw message ? */
+ bm->msg = strdup(p1);
+
+ if (debug)
+ printf("ASSUMING raw '%s' ", bm->msg);
+
+ break;
+#endif
+ }
+
+ p1 = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+ }
+ if (debug)
+ printf("\n");
+ if (has_fault)
+ goto discard_bm;
+
+ if (aif == NULL && beaconmode >= 0) {
+ if (debug)
+ printf("%s:%d Note: Lacking 'interface' keyword for this beacon definition. Beaconing to all Tx capable interfaces + APRSIS (mode depending)\n",
+ cf->name, cf->linenum);
+ }
+
+/*
+ if (srcaddr == NULL)
+ srcaddr = mycall;
+
+ if (srcaddr == NULL) {
+ if (debug)
+ printf("%s:%d Note: Lacking the 'for' keyword for this beacon definition.\n", cf->name, cf->linenum);
+ has_fault = 1;
+ goto discard_bm;
+ }
+*/
+
+ if (destaddr == NULL)
+ destaddr = tocall;
+
+ bm->src = srcaddr != NULL ? strdup(srcaddr) : NULL;
+ bm->dest = strdup(destaddr);
+ bm->via = via != NULL ? strdup(via) : NULL;
+ bm->interface = aif;
+ bm->beaconmode = beaconmode;
+
+ if (!bm->msg && !bm->filename && !bm->execfile) {
+ /* Not raw packet, perhaps composite ? */
+ if (!type) type = "!";
+ if (code && strlen(code) == 2 && lat && strlen(lat) == 8 &&
+ lon && strlen(lon) == 9) {
+ if ( strcmp(type,"!") == 0 ||
+ strcmp(type,"=") == 0 ) {
+ sprintf(buf, "%s%s%c%s%c%s", type, lat, code[0], lon,
+ code[1], comment ? comment : "");
+ } else if ( strcmp(type,"/") == 0 ||
+ strcmp(type,"@") == 0) {
+ sprintf(buf, "%s111111z%s%c%s%c%s", type, lat, code[0], lon,
+ code[1], comment ? comment : "");
+ } else if ( strcmp(type,";") == 0 && name) { // Object
+ sprintf(buf, ";%-9.9s*111111z%s%c%s%c%s", name, lat, code[0], lon,
+ code[1], comment ? comment : "");
+
+ } else if ( strcmp(type,")") == 0 && name) { // Item
+ sprintf(buf, ")%-3.9s!%s%c%s%c%s", name, lat, code[0], lon,
+ code[1], comment ? comment : "");
+ }
+ bm->msg = strdup(buf);
+ } else {
+ if (!code || (code && strlen(code) != 2))
+ printf("%s:%d .. BEACON definition failure; symbol parameter missing or wrong size\n", cf->name, cf->linenum);
+ if (!lat || (lat && strlen(lat) != 8))
+ printf("%s:%d .. BEACON definition failure; lat(itude) parameter missing or wrong size\n", cf->name, cf->linenum);
+ if (!lon || (lon && strlen(lon) != 9))
+ printf("%s:%d .. BEACON definition failure; lon(gitude) parameter missing or wrong size\n", cf->name, cf->linenum);
+ /* parse failure, abandon the alloc too */
+ has_fault = 1;
+ goto discard_bm;
+ }
+ }
+
+ if (debug) {
+ switch (beaconmode) {
+ case 1:
+ printf("RFONLY");
+ break;
+ case 0:
+ printf("RF+NET");
+ break;
+ default:
+ printf("NETONLY");
+ break;
+ }
+ printf(" BEACON FOR ");
+ if (srcaddr == NULL)
+ printf("***>%s", destaddr);
+ else
+ printf("%s>%s",srcaddr,destaddr);
+ if (via != NULL)
+ printf(",%s", via);
+ if (bm->filename)
+ printf("' file %s\n", bm->filename);
+ else
+ printf("' '%s'\n", bm->msg);
+ }
+
+ /* realloc() works also when old ptr is NULL */
+ bset->beacon_msgs = realloc(bset->beacon_msgs,
+ sizeof(bm) * (bset->beacon_msgs_count + 3));
+
+ bset->beacon_msgs[bset->beacon_msgs_count++] = bm;
+ bset->beacon_msgs[bset->beacon_msgs_count] = NULL;
+
+ if (bm->msg != NULL) { // Make this into AX.25 UI frame
+ // with leading control byte..
+ int len = strlen(bm->msg);
+ char *msg = realloc((void*)bm->msg, len+3); // make room
+ memmove(msg+2, msg, len+1); // move string end \0 also
+ msg[0] = 0x03; // Control byte
+ msg[1] = 0xF0; // PID 0xF0
+ bm->msg = msg;
+ }
+
+ beacon_reset(bset);
+
+ if (0) {
+ discard_bm:
+ if (bm->dest != NULL) free((void*)(bm->dest));
+ if (bm->msg != NULL) free((void*)(bm->msg));
+ free(bm);
+ }
+ return;
+}
+
+static void free_beaconmsg(struct beaconmsg *bmsg) {
+ if (bmsg == NULL) return;
+ if (bmsg->src) free((void*)bmsg->src);
+ if (bmsg->dest) free((void*)bmsg->dest);
+ if (bmsg->via) free((void*)bmsg->via);
+ if (bmsg->msg) free((void*)bmsg->msg);
+ if (bmsg->filename) free((void*)bmsg->filename);
+ if (bmsg->execfile) free((void*)bmsg->execfile);
+ free(bmsg);
+}
+
+static void free_beaconset(struct beaconset *bset) {
+ int i;
+ if (bset == NULL) return;
+ for (i = 0; i < bset->beacon_msgs_count; ++i) {
+ free_beaconmsg(bset->beacon_msgs[i]);
+ }
+ free(bset);
+}
+
+int beacon_config(struct configfile *cf)
+{
+ char *name, *param1;
+ char *str = cf->buf;
+ int beaconmode = 0;
+ int has_fault = 0;
+
+
+ struct beaconset *bset = calloc(1, sizeof(*bset));
+ bset->beacon_cycle_size = 20.0*60.0; // 20 minutes is the default
+
+ while (readconfigline(cf) != NULL) {
+ if (configline_is_comment(cf))
+ continue; /* Comment line, or empty line */
+
+ // It can be severely indented...
+ str = config_SKIPSPACE(cf->buf);
+
+ name = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+ config_STRLOWER(name);
+
+ param1 = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ if (strcmp(name, "</beacon>") == 0)
+ break;
+
+ if (strcmp(name, "cycle-size") == 0) {
+ int v;
+ if (config_parse_interval(param1, &v)) {
+ // Error
+ has_fault = 1;
+ continue;
+ }
+ bset->beacon_cycle_size = (float)v;
+ if (debug)
+ printf("Beacon cycle size: %.2f\n",
+ bset->beacon_cycle_size/60.0);
+ continue;
+ }
+
+ if (strcmp(name, "beacon") == 0) {
+ beacon_set(cf, param1, str, beaconmode, bset);
+
+ } else if (strcmp(name, "beaconmode") == 0) {
+ if (strcasecmp(param1, "both") == 0) {
+ beaconmode = 0;
+
+ } else if (strcasecmp(param1,"radio") == 0) {
+ beaconmode = 1;
+
+ } else if (strcasecmp(param1,"aprsis") == 0) {
+ beaconmode = -1;
+
+ } else {
+ printf("%s:%d ERROR: Unknown beaconmode parameter keyword: '%s'\n",
+ cf->name, cf->linenum, param1);
+ has_fault = 1;
+ }
+
+ } else {
+ printf("%s:%d ERROR: Unknown <beacon> block config keyword: '%s'\n",
+ cf->name, cf->linenum, name);
+ has_fault = 1;
+ continue;
+ }
+ }
+ if (has_fault) {
+ // discard it..
+ free_beaconset(bset);
+ } else {
+ // save it..
+ ++bsets_count;
+ bsets = realloc( bsets,sizeof(*bsets)*bsets_count );
+ bsets[bsets_count-1] = bset;
+
+ if (debug > 0) {
+ printf("<beacon> set %d defined with %d entries\n",
+ bsets_count, bset->beacon_msgs_count);
+ }
+ }
+
+ return has_fault;
+}
+
+static void fix_beacon_time(char *txt, int txtlen)
+{
+ int hour, min, sec;
+ char hms[8];
+
+ sec = now.tv_sec % (3600*24); // UNIX time is UTC -> no need to play with fancy timezone conversions and summer times...
+ hour = sec / 3600;
+ min = (sec / 60) % 60;
+ sec = sec % 60;
+ sprintf(hms, "%02d%02d%02dh", hour, min, sec);
+
+ txt += 2; txtlen -= 2; // Skip Control+PID
+
+ if (*txt == ';' && txtlen >= 36) { // Object
+
+ // ;434.775-B*111111z6044.06N/02612.79Er
+ memcpy( txt+11, hms, 7 ); // Overwrite with new time
+ } else if ((*txt == '/' || *txt == '@') && txtlen >= 27) { // Position with timestamp
+ memcpy( txt+1, hms, 7 ); // Overwrite with new time
+ }
+}
+
+
+static char *msg_read_file(const char *filename, char *buf, int buflen)
+{
+ FILE *fp = fopen(filename,"r");
+ if (!fp) return NULL;
+ if (fgets(buf, buflen, fp)) {
+ char *p = strchr(buf, '\n');
+ if (p) *p = 0;
+ } else {
+ *buf = 0;
+ }
+ fclose(fp);
+ if (*buf == 0) return NULL;
+ return buf;
+}
+
+static void beacon_resettimer(void *arg)
+{
+ const struct beaconset *bset = (struct beaconset *)arg;
+ float beacon_increment;
+ int i;
+ time_t t = tick.tv_sec;
+
+ srand((long)t);
+ beacon_increment = (bset->beacon_cycle_size / bset->beacon_msgs_count);
+
+ if (debug)
+ printf("beacons cycle: %.2f minutes, increment: %.2f minutes\n",
+ bset->beacon_cycle_size/60.0, beacon_increment/60.0);
+
+ for (i = 0; i < bset->beacon_msgs_count; ++i) {
+ int r = rand() % 1024;
+ int interval = (int)(beacon_increment - 0.2*beacon_increment * (r*0.001));
+ if (interval < 3) interval = 3; // Minimum interval: 3 seconds
+ t += interval;
+ if (debug)
+ printf("beacons offset: %.2f minutes\n", (t-tick.tv_sec)/60.0);
+ bset->beacon_msgs[i]->nexttime = t;
+ }
+}
+
+static void msg_exec_read(struct beaconset *bset)
+{
+ int rc;
+ int space = bset->exec_buf_space - bset->exec_buf_length;
+ if (debug) printf("msg_exec_read\n");
+
+ if (space < 1) {
+ space += 256;
+ bset->exec_buf_space += 256;
+ bset->exec_buf = realloc(bset->exec_buf, bset->exec_buf_space);
+ }
+
+ while ((rc = read(bset->exec_fd, bset->exec_buf + bset->exec_buf_length, space)) > 0) {
+ char *p;
+ bset->exec_buf_length += rc;
+ space -= rc;
+ p = memrchr(bset->exec_buf, '\n', bset->exec_buf_length);
+ if (p) {
+ if (debug) printf("found newline in exec read data\n");
+ *p = 0;
+ bset->exec_buf_length = p - bset->exec_buf;
+ struct beaconmsg *bm = bset->exec_bm;
+ if (bset->exec_buf_length > 2) {
+ // Run that beacon!
+ // Point it to read buffer
+ bm->msg = bset->exec_buf;
+ if (debug) printf(".. calling beacon_it() on buffer: %s\n", bm->msg+2);
+ beacon_it(bset, bm);
+ } else {
+ if (debug) printf(".. nothing read from exec pipe\n");
+ }
+ // erase the read buffer pointer
+ bm->msg = NULL;
+ // restore the nexttime
+ bset->beacon_nexttime.tv_sec = bm->nexttime;
+ close(bset->exec_fd);
+ bset->exec_fd = -1;
+ //bset->exec_pid = 0;
+ return;
+ }
+ if (debug) printf("no newline in exec read data\n");
+ if (space < 1) {
+ aprxlog("BEACON EXEC output overflowed read buffer.");
+ rc = 0; // simulate as if.. and kill it.
+ break;
+ }
+ }
+ if (rc == 0) { // EOF read
+ char *p;
+ if (debug) printf("Seen EOF on exec-read\n");
+ p = memrchr(bset->exec_buf, '\n', bset->exec_buf_length);
+ if (p) {
+ *p = 0;
+ bset->exec_buf_length = p - bset->exec_buf;
+ struct beaconmsg *bm = bset->exec_bm;
+ if (bset->exec_buf_length > 2) {
+ // Run that beacon!
+ // Point it to read buffer
+ bm->msg = bset->exec_buf;
+ if (debug) printf(".. calling beacon_it() on buffer: %s\n", bm->msg+2);
+ beacon_it(bset, bm);
+ } else {
+ if (debug) printf(".. nothing read from exec pipe\n");
+ }
+ // erase the read buffer pointer
+ bm->msg = NULL;
+ // restore the nexttime
+ bset->beacon_nexttime.tv_sec = bm->nexttime;
+ } else {
+ aprxlog("BEACON EXEC abnormal close.");
+ }
+ close(bset->exec_fd);
+ bset->exec_fd = -1;
+ //bset->exec_pid = 0;
+ }
+}
+
+static int msg_exec_file(const char *filename, int timeout, struct beaconset *bset)
+{
+ int p[2];
+ int pid;
+ int dev_null;
+ if (pipe(p)) {
+ return 0;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ close(p[0]);
+ close(p[1]);
+ return 0;
+ }
+ if (pid == 0) { //child
+
+ if (debug) fprintf(stderr,"execing child pid %d, file: %s\n", getpid(), filename);
+
+ close(p[0]);
+ if (p[1] != 1) {
+ dup2(p[1], 1);
+ close(p[1]);
+ }
+ dev_null = open("/dev/null", O_WRONLY);
+ if (debug && dev_null < 0)
+ fprintf(stderr,"child process: Failed to open file: /den/null\n");
+ if (dev_null >= 0) {
+ if (dev_null != 2) {
+ dup2(dev_null, 2);
+ close(dev_null);
+ }
+ dup2(2, 0);
+ }
+
+ //FIXME: change second parameter
+ execl(filename, "aprx", NULL);
+ if (debug)
+ fprintf(stderr,"child process: Failed to execute: %s\n", filename);
+ exit(255);
+
+ }
+
+ // parent
+
+ bset->exec_deadline = tick.tv_sec + timeout;
+ bset->exec_pid = pid;
+ bset->exec_fd = p[0];
+
+ bset->beacon_nexttime.tv_sec = bset->exec_deadline;
+
+ close(p[1]);
+
+ return 1;
+}
+
+// int val;
+// waitpid(pid, &val, 0);
+// if (WIFEXITED(val) && WEXITSTATUS(val) == 0) {
+// return buf;
+// }
+
+static void beacon_now(struct beaconset *bset)
+{
+ struct beaconmsg *bm;
+
+ if (bset->exec_pid > 0) {
+ if (debug) printf("beacon_now - still an exec under way.\n");
+ // Wait 3 seconds before retrying.
+ bset->beacon_nexttime.tv_sec += 3;
+ return;
+ }
+
+ if (bset->beacon_msgs_cursor >= bset->beacon_msgs_count) // Last done..
+ bset->beacon_msgs_cursor = 0;
+
+ if (bset->beacon_msgs_cursor == 0) {
+ beacon_resettimer(bset);
+ }
+
+ /* --- now the business of sending ... */
+
+ //if (debug) printf("beacon_now idx=%d\n", bset->beacon_msgs_cursor );
+ bm = bset->beacon_msgs[bset->beacon_msgs_cursor++];
+
+ bset->beacon_nexttime.tv_sec = bm->nexttime;
+ bset->beacon_nexttime.tv_usec = 0;
+
+ beacon_it(bset, bm);
+}
+
+static void beacon_it(struct beaconset *bset, struct beaconmsg *bm)
+{
+ int destlen;
+ int txtlen, msglen;
+ int i;
+ char const *txt;
+ char *msg;
+
+ if (debug)
+ printf("BEACON: idx=%d, nexttime= +%d sec\n",
+ bset->beacon_msgs_cursor-1, (int)(bset->beacon_nexttime.tv_sec - tick.tv_sec));
+
+ destlen = strlen(bm->dest) + ((bm->via != NULL) ? strlen(bm->via): 0) +2;
+
+ if (bm->filename != NULL) {
+ msg = alloca(256); // This is a load-and-discard allocation
+ txt = msg+2;
+ msg[0] = 0x03;
+ msg[1] = 0xF0;
+ if (!msg_read_file(bm->filename, msg+2, 256-2)) {
+ // Failed loading
+ if (debug)
+ printf("BEACON ERROR: Failed to load anything from file %s\n",bm->filename);
+ syslog(LOG_ERR, "Failed to load anything from beacon file %s", bm->filename);
+ return;
+ }
+ } else if (bm->msg != NULL) {
+ msg = (char*)bm->msg;
+ txt = bm->msg+2; // Skip Control+PID bytes
+ } else if (bm->execfile != NULL) {
+ bset->exec_buf = realloc(bset->exec_buf, 256);
+ bset->exec_buf[0] = 0x03;
+ bset->exec_buf[1] = 0xF0;
+ bset->exec_buf_length = 2;
+ bset->exec_buf_space = 256;
+ bset->exec_bm = bm;
+ if (!msg_exec_file(bm->execfile, bm->timeout, bset)) {
+ if (debug)
+ printf("BEACON ERROR: Failed to exec file %s\n",bm->execfile);
+ syslog(LOG_ERR, "Failed to exec file %s", bm->execfile);
+ return;
+ }
+ return; // spawning done, successfull or not..
+ } else {
+ if (debug) printf("Nothing to beacon now.\n");
+ return;
+ }
+
+ txtlen = strlen(txt);
+ msglen = txtlen+2; // this includes the control+pid bytes
+
+ /* _NO_ ending CRLF, the APRSIS subsystem adds it. */
+
+ /* Send those (rf)beacons.. (a noop if interface == NULL) */
+ if (bm->interface != NULL) {
+ const char *callsign = bm->interface->callsign;
+ const char *src = (bm->src != NULL) ? bm->src : callsign;
+ int len = destlen + 12 + strlen(src); // destlen contains bm->via plus room for ",TCPIP*"
+ char *destbuf = alloca(len);
+
+ // Now it is time to beacon something, lets make sure
+ // the source callsign is not APRSIS !
+ if (strcmp(src,"APRSIS") == 0) {
+ if (debug)
+ printf("CONFIGURATION ERROR: Beacon with source callsign APRSIS. Skipped!\n");
+ return;
+ }
+
+ if (bm->timefix)
+ fix_beacon_time(msg, msglen);
+
+#ifndef DISABLE_IGATE
+ if (bm->beaconmode <= 0) {
+
+ if (bm->via != NULL)
+ sprintf(destbuf,"%s>%s,%s,TCPIP*", src, bm->dest, bm->via);
+ else
+ sprintf(destbuf,"%s>%s,TCPIP*", src, bm->dest);
+
+ if (debug) {
+ printf("%ld\tNow beaconing to APRSIS %s '%s' -> '%s',",
+ tick.tv_sec, callsign, destbuf, txt);
+ printf(" next beacon in %.2f minutes\n",
+ ((bset->beacon_nexttime.tv_sec - tick.tv_sec)/60.0));
+ }
+
+ // Send them all also as netbeacons..
+ aprsis_queue(destbuf, strlen(destbuf),
+ qTYPE_LOCALGEN,
+ aprsis_login, txt, txtlen);
+ }
+#endif
+
+ if (bm->beaconmode >= 0 && bm->interface->tx_ok) {
+ // And to interfaces
+ char *dp = destbuf; // destbuf collects ONLY the VIA data
+
+ if (strcmp(src, callsign) != 0) {
+ if (bm->via != NULL)
+ dp += sprintf( dp, "%s*,%s", callsign, bm->via );
+ else
+ dp += sprintf( dp, "%s*", callsign );
+ } else {
+ if (bm->via != NULL)
+ dp += sprintf( dp, "%s", bm->via );
+ else
+ *dp = 0;
+ }
+
+ if (debug) {
+ printf("%ld\tNow beaconing to interface[1] %s(%s) '%s' -> '%s',",
+ tick.tv_sec, callsign, src, destbuf, txt);
+ printf(" next beacon in %.2f minutes\n",
+ ((bset->beacon_nexttime.tv_sec - tick.tv_sec)/60.0));
+ }
+
+ interface_transmit_beacon(bm->interface,
+ src,
+ bm->dest,
+ destbuf, // via data
+ msg, msglen);
+ }
+ }
+ else {
+ for ( i = 0; i < all_interfaces_count; ++i ) {
+ const struct aprx_interface *aif = all_interfaces[i];
+ const char *callsign = aif->callsign;
+ const char *src = (bm->src != NULL) ? bm->src : callsign;
+ int len = destlen + 12 + (src != NULL ? strlen(src) : 0); // destlen contains bm->via, plus room for ",TCPIP*"
+ char *destbuf = alloca(len);
+
+ if (debug>1)
+ printf("Beacon: aif=%p callsign='%s' src='%s' bm->dest='%s' bm->via='%s'\n",
+ aif, callsign, src, bm->dest, bm->via);
+
+ if (!interface_is_beaconable(aif)) {
+ if (debug>1)
+ printf("Not a beaconable interface, skipping\n");
+ continue; // it is not a beaconable interface
+ }
+
+ if (callsign == NULL) {
+ // Probably KISS master interface, and subIF 0 has no definition.
+ if (debug>1)
+ printf("No callsign on interface interface, skipping\n");
+ continue;
+ }
+
+ if (aif->iftype == IFTYPE_APRSIS) {
+ // If we have no radio interfaces, we may still
+ // want to do beacons to APRSIS. Ignore the
+ // builtin APRSIS interface if there are more
+ // interfaces available!
+ if (all_interfaces_count > 1) {
+ if (debug>2)
+ printf("Beaconing to APRSIS interface ignored in presence of other interfaces. Skipping.\n");
+ continue; // Ignore the builtin APRSIS interface
+ }
+ }
+
+ // Now it is time to beacon something, lets make sure
+ // the source callsign is not APRSIS !
+ if (strcmp(src,"APRSIS") == 0) {
+ if (debug)
+ printf("CONFIGURATION ERROR: Beaconing with source callsign APRSIS! Skipping.\n");
+ continue;
+ }
+
+
+ if (bm->timefix)
+ fix_beacon_time((char*)msg, msglen);
+
+#ifndef DISABLE_IGATE
+ if (bm->beaconmode <= 0) {
+ // Send them all also as netbeacons..
+
+ if (bm->via != NULL)
+ sprintf(destbuf,"%s>%s,%s,TCPIP*", src, bm->dest, bm->via);
+ else
+ sprintf(destbuf,"%s>%s,TCPIP*", src, bm->dest);
+
+ if (debug) {
+ printf("%ld\tNow beaconing to APRSIS %s(%s) '%s' -> '%s',",
+ tick.tv_sec, callsign, src, destbuf, txt);
+ printf(" next beacon in %.2f minutes\n",
+ ((bset->beacon_nexttime.tv_sec - tick.tv_sec)/60.0));
+ }
+
+ aprsis_queue(destbuf, strlen(destbuf),
+ qTYPE_LOCALGEN,
+ aprsis_login, txt, txtlen);
+ }
+#endif
+
+ if (bm->beaconmode >= 0 && aif->tx_ok) {
+ // And to transmit-capable interfaces
+ char *dp = destbuf; // destbuf collects ONLY the VIA data
+
+ // The 'destbuf' has a plenty of room
+ if (strcmp(src, callsign) != 0) {
+ if (bm->via != NULL)
+ dp += sprintf( dp, "%s*,%s", callsign, bm->via );
+ else
+ dp += sprintf( dp, "%s*", callsign );
+ } else {
+ if (bm->via != NULL)
+ dp += sprintf( dp, "%s", bm->via );
+ else
+ *dp = 0;
+ }
+
+ if (debug) {
+ printf("%ld\tNow beaconing to interface[2] %s(%s) '%s' -> '%s',",
+ tick.tv_sec, callsign, src, destbuf, txt);
+ printf(" next beacon in %.2f minutes\n",
+ ((bset->beacon_nexttime.tv_sec - tick.tv_sec)/60.0));
+ }
+
+ interface_transmit_beacon(aif,
+ src,
+ bm->dest,
+ destbuf, // via data
+ msg, msglen);
+ }
+ }
+ }
+}
+
+int beacon_prepoll(struct aprxpolls *app)
+{
+ int i;
+#ifndef DISABLE_IGATE
+ if (!aprsis_login)
+ return 0; /* No mycall ! hoh... */
+#endif
+ for (i = 0; i < bsets_count; ++i) {
+ struct beaconset *bset = bsets[i];
+ if (bset->beacon_msgs == NULL) continue; // nothing here
+
+ if (time_reset) {
+ // master time pickup noticed time back-tracking
+ beacon_resettimer(bset);
+ }
+
+ if (tv_timercmp(&bset->beacon_nexttime, &app->next_timeout) < 0)
+ app->next_timeout = bset->beacon_nexttime;
+
+ if (bset->exec_pid != 0 && bset->exec_fd >= 0) {
+ struct pollfd *pfd;
+ // FD is open, lets mark it for poll read..
+ pfd = aprxpolls_new(app);
+ pfd->fd = bset->exec_fd;
+ pfd->events = POLLIN | POLLPRI;
+ pfd->revents = 0;
+ }
+ }
+
+ return 0; /* No poll descriptors, only time.. */
+}
+
+
+int beacon_postpoll(struct aprxpolls *app)
+{
+ int idx, i;
+ //struct serialport *S;
+ struct pollfd *P;
+#ifndef DISABLE_IGATE
+ if (!aprsis_login)
+ return 0; /* No mycall ! hoh... */
+#endif
+ for (i = 0; i < bsets_count; ++i) {
+ struct beaconset *bset = bsets[i];
+ if (bset->exec_pid > 0 && bset->exec_deadline < tick.tv_sec) {
+ // Waited too long, discard it.
+ //printf("killing subprogram pid=%d mypid=%d\n", bset->exec_pid, getpid());
+ if (debug) printf("Killing overdue beacon exec subprogram pid %d\n", bset->exec_pid);
+ kill(bset->exec_pid, SIGKILL);
+ bset->exec_pid = - bset->exec_pid;
+ }
+ for (idx = 0, P = app->polls; idx < app->pollcount; ++idx, ++P) {
+ if (bset->exec_fd == P->fd) {
+ if (debug) printf("revents of exec_fd = 0x%x\n", P->revents);
+ if (P->revents & (POLLIN | POLLPRI | POLLHUP)) {
+ msg_exec_read(bset);
+ }
+ }
+ }
+ if (bset->beacon_msgs == NULL) continue; // nothing..
+ if (tv_timercmp(&bset->beacon_nexttime, &tick) > 0) continue; // not yet
+
+ beacon_now(bset);
+ }
+
+ if (debug) printf("beacon_postpoll()\n");
+
+
+ return 0;
+}
+
+void beacon_childexit(int pid)
+{
+ int i;
+ for (i = 0; i < bsets_count; ++i) {
+ struct beaconset *bset = bsets[i];
+ if (pid == bset->exec_pid) {
+ bset->exec_pid = -pid;
+ if (debug) {
+ // Avoid stdio FILE* interlocks within signal handler
+ char buf[64];
+ sprintf(buf, "matched child exit, pid=%d\n", pid);
+ write(1, buf, strlen(buf));
+ }
+ break;
+ }
+ }
+}
diff --git a/build-stamp b/build-stamp
new file mode 100644
index 0000000..e69de29
diff --git a/cellmalloc.c b/cellmalloc.c
new file mode 100644
index 0000000..b999109
--- /dev/null
+++ b/cellmalloc.c
@@ -0,0 +1,357 @@
+/********************************************************************
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ ********************************************************************/
+
+#include "config.h"
+
+#include <stdio.h>
+#ifdef HAVE_STDLIB_H
+# include <stdlib.h>
+#endif
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#include <string.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+// #if defined(HAVE_PTHREAD_CREATE) && defined(ENABLE_PTHREAD)
+// #include <pthread.h>
+// #endif
+
+#include "cellmalloc.h"
+
+#define NO_MMAP_ON_CELLMALLOC
+#define MEMDEBUG
+
+/*
+ * cellmalloc() -- manages arrays of cells of data
+ *
+ */
+
+struct cellhead;
+
+struct cellarena_t {
+ int cellsize;
+ int alignment;
+ int increment; /* alignment overhead applied.. */
+ int lifo_policy;
+ int minfree;
+
+ const char *arenaname;
+
+// pthread_mutex_t mutex; // we have a mutex-less usage environment!
+
+ struct cellhead *free_head;
+ struct cellhead *free_tail;
+
+ int freecount;
+ int createsize;
+
+#ifdef MEMDEBUG
+ int cellblocks_count;
+#define CELLBLOCKS_MAX 40 /* track client cell allocator limit! */
+ char *cellblocks[CELLBLOCKS_MAX]; /* ref as 'char pointer' for pointer arithmetics... */
+#endif
+};
+
+#define CELLHEAD_DEBUG 0
+
+struct cellhead {
+#if CELLHEAD_DEBUG == 1
+ struct cellarena_t *ca;
+#endif
+ struct cellhead *next;
+};
+
+
+/*
+ * new_cellblock() -- must be called MUTEX PROTECTED
+ *
+ */
+
+int new_cellblock(cellarena_t *ca)
+{
+ int i;
+ char *cb;
+
+#ifdef MEMDEBUG /* External backing-store files, unique ones for each cellblock,
+ which at Linux names memory blocks in /proc/nnn/smaps "file"
+ with this filename.. */
+ int fd;
+ char name[2048];
+
+ sprintf(name, "/tmp/.-%d-%s-%d.mmap", getpid(), ca->arenaname, ca->cellblocks_count );
+ unlink(name);
+ fd = open(name, O_RDWR|O_CREAT, 644);
+ unlink(name);
+ if (fd >= 0) {
+ memset(name, 0, sizeof(name));
+ i = 0;
+ while (i < ca->createsize) {
+ int rc = write(fd, name, sizeof(name));
+ if (rc < 0) break;
+ i += rc;
+ }
+ }
+
+ cb = mmap( NULL, ca->createsize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
+ if (fd >= 0)
+ close(fd);
+#else
+
+#ifndef MAP_ANON
+# define MAP_ANON 0
+#endif
+#ifdef NO_MMAP_ON_CELLMALLOC
+ cb = malloc( ca->createsize );
+#else
+ cb = mmap( NULL, ca->createsize, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0);
+#endif
+#endif
+ if (cb == NULL || cb == (char*)-1)
+ return -1;
+
+#ifdef MEMDEBUG
+ if (ca->cellblocks_count >= CELLBLOCKS_MAX) return -1;
+ ca->cellblocks[ca->cellblocks_count++] = cb;
+#endif
+
+ for (i = 0; i <= ca->createsize-ca->increment; i += ca->increment) {
+ struct cellhead *ch = (struct cellhead *)(cb + i); /* pointer arithmentic! */
+ if (!ca->free_head) {
+ ca->free_head = ch;
+ } else {
+ ca->free_tail->next = ch;
+ }
+ ca->free_tail = ch;
+ ch->next = NULL;
+#if CELLHEAD_DEBUG == 1
+ ch->ca = ca; // cellhead pointer space
+#endif
+
+ ca->freecount += 1;
+ }
+
+ return 0;
+}
+
+
+
+/*
+ * cellinit() -- the main program calls this once for each used cell type/size
+ *
+ */
+
+
+cellarena_t *cellinit( const char *arenaname, const int cellsize, const int alignment, const int policy, const int createkb, const int minfree )
+{
+ cellarena_t *ca = calloc(1, sizeof(*ca));
+ // int n;
+
+ ca->arenaname = arenaname;
+
+#if CELLHEAD_DEBUG == 1
+ if (alignment < __alignof__(void*))
+ alignment = __alignof__(void*); // cellhead pointer space
+#endif
+
+ ca->cellsize = cellsize;
+ ca->alignment = alignment;
+ ca->minfree = minfree;
+#if CELLHEAD_DEBUG == 1
+ ca->increment = cellsize + sizeof(void*); // cellhead pointer space
+#else
+ ca->increment = cellsize;
+#endif
+ if ((cellsize % alignment) != 0) {
+ ca->increment += alignment - cellsize % alignment;
+ }
+ ca->lifo_policy = policy & CELLMALLOC_POLICY_LIFO;
+
+ ca->createsize = createkb * 1024;
+#if !defined(MEMDEBUG) && defined(NO_MMAP_ON_CELLMALLOC)
+ ca->createsize -= 16;
+#endif
+
+ // n = ca->createsize / ca->increment;
+ // hlog( LOG_DEBUG, "cellinit: %-12s block size %4d kB, cells/block: %d", arenaname, createkb, n );
+
+// pthread_mutex_init(&ca->mutex, NULL);
+
+ new_cellblock(ca); /* First block of cells, not yet need to be mutex protected */
+ while (ca->freecount < ca->minfree)
+ new_cellblock(ca); /* more until minfree is full */
+
+#if CELLHEAD_DEBUG == 1
+ // hlog(LOG_DEBUG, "cellinit() cellhead=%p", ca);
+#endif
+ return ca;
+}
+
+
+inline void *cellhead_to_clientptr(struct cellhead *ch)
+{
+ char *p = (char*)ch;
+#if CELLHEAD_DEBUG == 1
+ p += sizeof(void*);
+#endif
+ return p;
+}
+
+inline struct cellhead *clientptr_to_cellhead(void *v)
+{
+#if CELLHEAD_DEBUG == 1
+ struct cellhead *ch = (struct cellhead *)(((char*)v) - sizeof(void*));
+#else
+ struct cellhead *ch = (struct cellhead*)v;
+#endif
+ return ch;
+}
+
+
+void *cellmalloc(cellarena_t *ca)
+{
+ void *cp;
+ struct cellhead *ch;
+
+ while (!ca->free_head || (ca->freecount < ca->minfree))
+ if (new_cellblock(ca)) {
+// pthread_mutex_unlock(&ca->mutex);
+ return NULL;
+ }
+
+ /* Pick new one off the free-head ! */
+ ch = ca->free_head;
+ ca->free_head = ch->next;
+ ch->next = NULL;
+ cp = ch;
+ if (ca->free_head == NULL)
+ ca->free_tail = NULL;
+
+ ca->freecount -= 1;
+
+ // hlog(LOG_DEBUG, "cellmalloc(%p at %p) freecount %d", cellhead_to_clientptr(cp), ca, ca->freecount);
+ return cellhead_to_clientptr(cp);
+}
+
+/*
+ * cellmallocmany() -- give many cells in single lock region
+ *
+ */
+
+int cellmallocmany(cellarena_t *ca, void **array, int numcells)
+{
+ int count;
+ struct cellhead *ch;
+
+ for (count = 0; count < numcells; ++count) {
+
+ while (!ca->free_head ||
+ ca->freecount < ca->minfree) {
+ /* Out of free cells ? alloc new set */
+ if (new_cellblock(ca)) {
+ /* Failed ! */
+ break;
+ }
+ }
+
+ /* Pick new one off the free-head ! */
+
+ ch = ca->free_head;
+
+ // hlog( LOG_DEBUG, "cellmallocmany(%d of %d); freecount %d; %p at %p",
+ // count, numcells, ca->freecount, cellhead_to_clientptr(ch), ca );
+
+ if (ch != NULL) { // should always be...
+ ca->free_head = ch->next;
+ ch->next = NULL;
+ }
+
+ if (ca->free_head == NULL)
+ ca->free_tail = NULL;
+
+ array[count] = cellhead_to_clientptr(ch);
+
+ ca->freecount -= 1;
+
+ }
+
+ return count;
+}
+
+
+
+void cellfree(cellarena_t *ca, void *p)
+{
+ struct cellhead *ch = clientptr_to_cellhead(p);
+ ch->next = NULL;
+#if CELLHEAD_DEBUG == 1
+ if (ch->ca != ca) {
+ // hlog(LOG_ERR, "cellfree(%p to %p) wrong cellhead->ca pointer %p", p, ca, ch->ca);
+ }
+#endif
+
+ // hlog(LOG_DEBUG, "cellfree() %p to %p", p, ca);
+
+ if (ca->lifo_policy) {
+ /* Put the cell on free-head */
+ ch->next = ca->free_head;
+ ca->free_head = ch;
+
+ } else {
+ /* Put the cell on free-tail */
+ if (ca->free_tail)
+ ca->free_tail->next = ch;
+ ca->free_tail = ch;
+ if (!ca->free_head)
+ ca->free_head = ch;
+ ch->next = NULL;
+ }
+
+ ca->freecount += 1;
+}
+
+/*
+ * cellfreemany() -- release many cells in single lock region
+ *
+ */
+
+void cellfreemany(cellarena_t *ca, void **array, int numcells)
+{
+ int count;
+
+ for (count = 0; count < numcells; ++count) {
+
+ struct cellhead *ch = clientptr_to_cellhead(array[count]);
+
+#if CELLHEAD_DEBUG == 1
+ if (ch->ca != ca) {
+ // hlog(LOG_ERR, "cellfreemany(%p to %p) wrong cellhead->ca pointer %p", array[count], ca, ch->ca);
+ }
+#endif
+
+ // hlog(LOG_DEBUG, "cellfreemany() %p to %p", ch, ca);
+
+ if (ca->lifo_policy) {
+ /* Put the cell on free-head */
+ ch->next = ca->free_head;
+ ca->free_head = ch;
+
+ } else {
+ /* Put the cell on free-tail */
+ if (ca->free_tail)
+ ca->free_tail->next = ch;
+ ca->free_tail = ch;
+ if (!ca->free_head)
+ ca->free_head = ch;
+ ch->next = NULL;
+ }
+
+ ca->freecount += 1;
+ }
+}
diff --git a/cellmalloc.h b/cellmalloc.h
new file mode 100644
index 0000000..dd90a93
--- /dev/null
+++ b/cellmalloc.h
@@ -0,0 +1,31 @@
+/********************************************************************
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ ********************************************************************/
+
+#ifndef _CELLMALLOC_H_
+#define _CELLMALLOC_H_
+
+/*
+ * cellmalloc() -- manages arrays of cells of data
+ *
+ */
+
+typedef struct cellarena_t cellarena_t;
+
+extern cellarena_t *cellinit(const char *arenaname, const int cellsize, const int alignment, const int policy, const int createkb, const int minfree);
+
+#define CELLMALLOC_POLICY_FIFO 0
+#define CELLMALLOC_POLICY_LIFO 1
+#define CELLMALLOC_POLICY_NOMUTEX 2
+
+extern void *cellmalloc(cellarena_t *cellarena);
+extern int cellmallocmany(cellarena_t *cellarena, void **array, const int numcells);
+extern void cellfree(cellarena_t *cellarena, void *p);
+extern void cellfreemany(cellarena_t *cellarena, void **array, const int numcells);
+
+#endif
diff --git a/config.c b/config.c
new file mode 100644
index 0000000..7efb98e
--- /dev/null
+++ b/config.c
@@ -0,0 +1,814 @@
+/* **************************************************************** *
+ * *
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ * **************************************************************** */
+
+#include "aprx.h"
+
+#ifndef DISABLE_IGATE
+const char *aprsis_login;
+#endif
+
+
+char *config_SKIPSPACE(char *Y)
+{
+ assert(Y != NULL);
+ while (*Y == ' ' || *Y == '\t')
+ ++Y;
+ return Y;
+}
+
+#if 0
+char *config_SKIPDIGIT(char *Y)
+{
+ assert(Y != NULL);
+ while ('0' <= *Y && *Y <= '9')
+ ++Y;
+ return Y;
+}
+#endif
+
+// return 0 for failures, 1 for OK.
+int validate_callsign_input(char *callsign, int strict)
+{
+ int i = strlen(callsign);
+ char *p = callsign;
+ char c = 0;
+ int seen_minus = 0;
+ int ssid = 0;
+
+ for ( ; *p ; ++p ) {
+ c = *p;
+ if ('a' <= c && c <= 'z') {
+ // Presuming ASCII
+ c -= ('a'-'A');
+ *p = c; // Side-effect: translates the callsign to uppercase
+ }
+ if (!seen_minus && c == '-') {
+ if (p == callsign || p[1] == 0)
+ return 0; // Hyphen is at beginning or at end!
+ if ((p - callsign) > 6)
+ return 0; // Hyphen too far! Max 6 characters preceding it.
+ seen_minus = 1;
+ continue;
+ } else if (seen_minus && c == '-') {
+ return 0; // Seen a hyphen again!
+ }
+
+ // The "SSID" value can be alphanumeric here!
+
+ if (!seen_minus) {
+ // Callsign prefix
+ if (('A' <= c && c <= 'Z') || ('0' <= c && c <= '9')) {
+ // Valid character!
+ } else {
+ return 0; // Invalid characters in callsign part
+ }
+ } else {
+ // SSID tail
+ if (strict) {
+ if ('0' <= c && c <= '9') {
+ // Valid character!
+ ssid = ssid * 10 + c - '0';
+ if (ssid > 15) { // SSID value range: 0 .. 15
+ return 0;
+ }
+ } else {
+ return 0; // Invalid characters in SSID part
+ }
+ } else { // non-strict
+ if (('A' <= c && c <= 'Z') || ('0' <= c && c <= '9')) {
+ // Valid character!
+ } else {
+ return 0; // Invalid characters in SSID part
+ }
+ }
+ }
+ }
+ if (i > 3 && (callsign[i - 1] == '0' && callsign[i - 2] == '-')) {
+ callsign[i - 2] = 0;
+ /* If tailed with "-0" SSID, chop it off.. */
+ }
+ return 1;
+}
+
+/* SKIPTEXT:
+ *
+ * Detect "/' -> scan until matching double quote
+ * Process \-escapes on string: \xFD, \n, \", \'
+ * Detect non-eol, non-space(tab): scan until eol, or white-space
+ * No \-escapes
+ *
+ * Will thus stop when found non-quoted space/tab, or
+ * end of line/string.
+ */
+
+char *config_SKIPTEXT(char *Y, int *lenp)
+{
+ char *O;
+ char endc;
+ int len;
+
+ assert(Y != NULL);
+
+ O = Y;
+ endc = *Y;
+ len = 0;
+
+ if (*Y == '"' || *Y == '\'') {
+ ++Y;
+ while (*Y && *Y != endc) {
+ if (*Y == '\\') {
+ /* backslash escape.. */
+ ++Y;
+ if (*Y == 'n') {
+ *O++ = '\n';
+ ++len;
+ } else if (*Y == 'r') {
+ *O++ = '\r';
+ ++len;
+ } else if (*Y == '"') {
+ *O++ = '"';
+ ++len;
+ } else if (*Y == '\'') {
+ *O++ = '\'';
+ } else if (*Y == '\\') {
+ *O++ = '\\';
+ } else if (*Y == 'x') {
+ /* Hex encoded char */
+ int i;
+ char hx[3];
+ if (*Y)
+ ++Y;
+ hx[0] = *Y;
+ if (*Y)
+ ++Y;
+ hx[1] = *Y;
+ hx[2] = 0;
+ i = (int) strtol(hx, NULL, 16);
+ *O++ = i;
+ ++len;
+ }
+ } else {
+ *O++ = *Y;
+ ++len;
+ }
+ if (*Y != 0)
+ ++Y;
+ }
+ if (*Y == endc)
+ ++Y;
+ *O = 0; /* String end */
+ /* STOP at the tail-end " */
+ } else {
+ while (*Y && *Y != ' ' && *Y != '\t') {
+ ++Y;
+ ++len;
+ }
+ /* Stop at white-space or end */
+ if (*Y)
+ *Y++ = 0;
+ }
+
+ if (lenp != NULL)
+ *lenp = len;
+ return Y;
+}
+
+void config_STRLOWER(char *s)
+{
+ int c;
+ assert(s != NULL);
+ for (; *s; ++s) {
+ c = *s;
+ if ('A' <= c && c <= 'Z') {
+ *s = c + ('a' - 'A');
+ }
+ }
+}
+
+void config_STRUPPER(char *s)
+{
+ int c;
+ assert(s != NULL);
+ for (; *s; ++s) {
+ c = *s;
+ if ('a' <= c && c <= 'z') {
+ *s = c + ('A' - 'a');
+ }
+ }
+}
+
+static int logging_config(struct configfile *cf)
+{
+ char *name, *param1;
+ char *str = cf->buf;
+ int has_fault = 0;
+
+ while (readconfigline(cf) != NULL) {
+ if (configline_is_comment(cf))
+ continue; /* Comment line, or empty line */
+
+ str = cf->buf;
+ str = config_SKIPSPACE(str); // arbitrary indention
+ name = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+ config_STRLOWER(name);
+
+ param1 = str;
+
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ if (strcmp(name, "</logging>") == 0)
+ break;
+
+ if (strcmp(name, "aprxlog") == 0) {
+ if (debug)
+ printf("%s:%d: INFO: APRXLOG = '%s' '%s'\n",
+ cf->name, cf->linenum, param1, str);
+
+ aprxlogfile = strdup(param1);
+
+#ifndef DISABLE_IGATE
+ } else if (strcmp(name, "dprslog") == 0) {
+ if (debug)
+ printf("%s:%d: INFO: DPRSLOG = '%s' '%s'\n",
+ cf->name, cf->linenum, param1, str);
+
+ dprslogfile = strdup(param1);
+#endif
+
+ } else if (strcmp(name, "rflog") == 0) {
+ if (debug)
+ printf("%s:%d: INFO: RFLOG = '%s' '%s'\n",
+ cf->name, cf->linenum, param1, str);
+
+ rflogfile = strdup(param1);
+
+ } else if (strcmp(name, "pidfile") == 0) {
+ if (debug)
+ printf("%s:%d: INFO: PIDFILE = '%s' '%s'\n",
+ cf->name, cf->linenum, param1, str);
+
+ pidfile = strdup(param1);
+
+ } else if (strcmp(name, "erlangfile") == 0) {
+ if (debug)
+ printf("%s:%d: INFO: ERLANGFILE = '%s' '%s'\n",
+ cf->name, cf->linenum, param1, str);
+
+ erlang_backingstore = strdup(param1);
+
+ } else if (strcmp(name, "erlang-loglevel") == 0) {
+ if (debug)
+ printf("%s:%d: INFO: ERLANG-LOGLEVEL = '%s' '%s'\n",
+ cf->name, cf->linenum, param1, str);
+ erlang_init(param1);
+
+ } else if (strcmp(name, "erlanglog") == 0) {
+ if (debug)
+ printf("%s:%d: INFO: ERLANGLOG = '%s'\n",
+ cf->name, cf->linenum, param1);
+
+ erlanglogfile = strdup(param1);
+
+ } else if (strcmp(name, "erlang-log1min") == 0) {
+ if (debug)
+ printf("%s:%d: INFO: ERLANG-LOG1MIN\n",
+ cf->name, cf->linenum);
+
+ erlanglog1min = 1;
+
+ } else {
+ printf("%s:%d: ERROR: Unknown <logging> keyword: '%s' '%s'\n",
+ cf->name, cf->linenum, name, param1);
+ has_fault = 1;
+ }
+ }
+ return has_fault;
+}
+
+static int cfgparam(struct configfile *cf)
+{
+ char *name, *param1;
+ char *str = cf->buf;
+
+ str = config_SKIPSPACE(str); // arbitrary indention
+ name = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+ config_STRLOWER(name);
+
+ param1 = str;
+
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+#ifndef DISABLE_IGATE
+ if (strcmp(name, "<aprsis>") == 0) {
+ return aprsis_config(cf);
+ }
+#endif
+ if (strcmp(name, "<interface>") == 0) {
+ return interface_config(cf);
+ }
+ if (strcmp(name, "<telemetry>") == 0) {
+ return telemetry_config(cf);
+ }
+ if (strcmp(name, "<digipeater>") == 0) {
+ return digipeater_config(cf);
+ }
+ if (strcmp(name, "<beacon>") == 0) {
+ return beacon_config(cf);
+ }
+ if (strcmp(name, "<logging>") == 0) {
+ return logging_config(cf);
+ }
+
+
+ if (strcmp(name, "mycall") == 0) {
+ config_STRUPPER(param1);
+ // Store these always, it helps with latter error diagnostics
+ mycall = strdup(param1);
+#ifndef DISABLE_IGATE
+ aprsis_login = mycall;
+#endif
+ if (validate_callsign_input(param1,1)) {
+ if (debug)
+ printf("%s:%d: MYCALL = '%s' '%s'\n",
+ cf->name, cf->linenum, mycall, str);
+ } else {
+ if (validate_callsign_input(param1,0)) {
+ printf("%s:%d: MYCALL = '%s' value is OK for APRSIS login, and Rx-IGate, but not valid AX.25 node callsign.\n",
+ cf->name, cf->linenum, param1);
+
+ } else {
+ // but sometimes the parser yields an error!
+ printf("%s:%d: MYCALL = '%s' value is not valid AX.25 node callsign, nor valid for APRSIS login.\n",
+ cf->name, cf->linenum, param1);
+ return 1;
+ }
+ }
+
+ } else if (strcmp(name, "myloc") == 0) {
+ // lat xx lon yy
+ char *latp;
+ char *lonp;
+ float lat, lng;
+ int i, la, lo;
+ char lac, loc;
+
+ const char *const errmsg = "%s:%d: myloc parameters wrong, expected format: 'myloc' 'lat' 'ddmm.mmN' 'lon' 'dddmm.mmE'\n";
+
+ if (strcmp(param1, "lat") != 0) {
+ printf(errmsg, cf->name, cf->linenum);
+ printf(" .. 'lat' missing, got: '%s'\n", param1);
+ return 1;
+ }
+
+ latp = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ param1 = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ if (strcmp(param1, "lon") != 0) {
+ printf(errmsg, cf->name, cf->linenum);
+ printf(" .. 'lon' missing, got: '%s'\n", param1);
+ return 1;
+ }
+
+ lonp = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ if (validate_degmin_input(latp, 90)) {
+ printf(errmsg, cf->name, cf->linenum);
+ printf(" got lat: '%s'\n", latp);
+ return 1;
+ }
+ if (validate_degmin_input(lonp, 180)) {
+ printf(errmsg, cf->name, cf->linenum);
+ printf(" got lon: '%s'\n", lonp);
+ return 1;
+ }
+
+ i = sscanf(latp, "%2d%5f%c,", &la, &lat, &lac);
+ if (i != 3) {
+ printf(errmsg, cf->name, cf->linenum);
+ printf(" got parse-field-count: %d on '%s'\n", i, latp);
+ return 1; // parse failure
+ }
+ i = sscanf(lonp, "%3d%5f%c,", &lo, &lng, &loc);
+ if (i != 3) {
+ printf(errmsg, cf->name, cf->linenum);
+ printf(" got parse-field-count: %d on '%s'\n", i, lonp);
+ return 1; // parse failure
+ }
+
+ if (lac != 'N' && lac != 'S' && lac != 'n' && lac != 's') {
+ printf(errmsg, cf->name, cf->linenum);
+ printf(" .. lat expected N/S tail, got: '%c'\n", lac);
+ return 1; // bad indicator value
+ }
+ if (loc != 'E' && loc != 'W' && loc != 'e' && loc != 'w') {
+ printf(errmsg, cf->name, cf->linenum);
+ printf(" .. lon expected E/W tail, got: '%c'\n", loc);
+ return 1; // bad indicator value
+ }
+
+ myloc_latstr = strdup(latp);
+ myloc_lonstr = strdup(lonp);
+
+ myloc_lat = (float)la + lat/60.0;
+ myloc_lon = (float)lo + lng/60.0;
+
+ if (lac == 'S' || lac == 's')
+ myloc_lat = -myloc_lat;
+ if (loc == 'W' || loc == 'w')
+ myloc_lon = -myloc_lon;
+
+ if (debug)
+ printf("%s:%d: MYLOC LAT %8.5f degrees LON %8.5f degrees\n",
+ cf->name, cf->linenum, myloc_lat, myloc_lon);
+
+ myloc_lat = filter_lat2rad(myloc_lat);
+ myloc_lon = filter_lon2rad(myloc_lon);
+ myloc_coslat = cos(myloc_lat);
+
+
+#ifndef DISABLE_IGATE
+ } else if (strcmp(name, "aprsis-login") == 0) {
+
+ printf("%s:%d WARNING: Old-style top-level 'aprsis-login' definition, it should be inside <aprsis> group tags.\n",
+ cf->name, cf->linenum);
+
+ config_STRUPPER(param1);
+ aprsis_login = strdup(param1);
+ if (validate_callsign_input(param1,0)) {
+ if (debug)
+ printf("%s:%d: APRSIS-LOGIN = '%s' '%s'\n",
+ cf->name, cf->linenum, aprsis_login, str);
+ } else {
+ printf("%s:%d: APRSIS-LOGIN = '%s' value is not valid AX25-like node'\n",
+ cf->name, cf->linenum, aprsis_login);
+ return 1;
+ }
+
+ } else if (strcmp(name, "aprsis-server") == 0) {
+
+ printf("%s:%d WARNING: Old-style top-level 'aprsis-server' definition, it should be inside <aprsis> group tags.\n",
+ cf->name, cf->linenum);
+
+ if (debug)
+ printf("%s:%d: APRSIS-SERVER = '%s':'%s'\n",
+ cf->name, cf->linenum, param1, str);
+
+ return aprsis_add_server(param1, str);
+
+ } else if (strcmp(name, "aprsis-heartbeat-timeout") == 0) {
+ int i = atoi(param1);
+ if (i < 0) /* param failure ? */
+ i = 0; /* no timeout */
+
+ printf("%s:%d WARNING: Old-style top-level 'aprsis-heartbeat-timeout' definition, it should be inside <aprsis> group tags.\n",
+ cf->name, cf->linenum);
+
+ if (debug)
+ printf("%s:%d: APRSIS-HEARTBEAT-TIMEOUT = '%d' '%s'\n",
+ cf->name, cf->linenum, i, str);
+
+ return aprsis_set_heartbeat_timeout(i);
+
+
+ } else if (strcmp(name, "aprsis-filter") == 0) {
+
+ printf("%s:%d WARNING: Old-style top-level 'aprsis-filter' definition, it should be inside <aprsis> group tags.\n",
+ cf->name, cf->linenum);
+
+ return aprsis_set_filter(param1);
+#endif
+
+#ifdef PF_AX25 /* PF_AX25 exists -- highly likely a Linux system ! */
+ } else if (strcmp(name, "ax25-rxport") == 0) {
+
+ printf("%s:%d WARNING: Old-style top-level 'ax25-rxport' definition. See <interface> groups, 'ax25-device' definitions.\n",
+ cf->name, cf->linenum);
+
+ if (debug)
+ printf("%s:%d: AX25-RXPORT '%s' '%s'\n",
+ cf->name, cf->linenum, param1, str);
+
+ return (netax25_addrxport(param1, NULL) == NULL);
+#endif
+ } else if (strcmp(name, "radio") == 0) {
+
+ printf("%s:%d WARNING: Old-style top-level 'radio' definition. See <interface> groups, 'serial-device' or 'tcp-device' definitions.\n",
+ cf->name, cf->linenum);
+
+ if (debug)
+ printf("%s:%d: RADIO = %s %s..\n",
+ cf->name, cf->linenum, param1, str);
+ return (ttyreader_serialcfg(cf, param1, str) == NULL);
+
+ } else if (strcmp(name, "ax25-device") == 0) {
+ printf("%s:%d ERROR: The 'ax25-device' entry must be inside an <interface> group tag.\n",
+ cf->name, cf->linenum);
+ return 1;
+
+ } else if (strcmp(name, "serial-device") == 0) {
+ printf("%s:%d ERROR: The 'serial-device' entry must be inside an <interface> group tag.\n",
+ cf->name, cf->linenum);
+ return 1;
+
+ } else if (strcmp(name, "tcp-device") == 0) {
+ printf("%s:%d ERROR: The 'tcp-device' entry must be inside an <interface> group tag.\n",
+ cf->name, cf->linenum);
+ return 1;
+
+ } else if (strcmp(name, "beacon") == 0) {
+ printf("%s:%d ERROR: The 'beacon' entry must be inside a <beacon> group tag.\n",
+ cf->name, cf->linenum);
+ return 1;
+
+ } else {
+ printf("%s:%d: ERROR: Unknown config keyword: '%s' '%s'\n",
+ cf->name, cf->linenum, name, param1);
+ printf("%s:%d: Perhaps this is due to lack of some surrounding <group> tag ?\n",
+ cf->name, cf->linenum);
+ return 1;
+ }
+ return 0;
+}
+
+
+const char* scan_int(const char *p, int len, int *val, int *seen_space)
+{
+ int i;
+ char c;
+ *val = 0;
+ for (i = 0; i < len; ++i, ++p) {
+ c = *p;
+ if (('0' <= c && c <= '9') && !(*seen_space)) {
+ *val = (*val) * 10 + (c - '0');
+ } else if (c == ' ') {
+ *val = (*val) * 10;
+ *seen_space = 1;
+ } else {
+ return NULL;
+ }
+ }
+ return p;
+}
+
+int validate_degmin_input(const char *s, int maxdeg)
+{
+ int deg;
+ int m1, m2;
+ char c;
+ const char *t;
+ int seen_space = 0;
+ if (maxdeg > 90) {
+ t = scan_int(s, 3, °, &seen_space);
+ if (t != s+3) return 1; // scan failure
+ if (deg > 179) return 1; // too large value
+ s = t;
+ t = scan_int(s, 2, &m1, &seen_space);
+ if (t != s+2) return 1;
+ if (m1 > 59) return 1;
+ s = t;
+ c = *s;
+ if (!seen_space && c == '.') {
+ // OK
+ } else if (!seen_space && c == ' ') {
+ seen_space = 1;
+ } else {
+ return 1; // Bad char..
+ }
+ ++s;
+ t = scan_int(s, 2, &m2, &seen_space);
+ if (t != s+2) return 1;
+ s = t;
+ c = *s;
+ if (c != 'E' && c != 'e' && c != 'W' && c != 'w') return 1;
+ } else {
+ t = scan_int(s, 2, °, &seen_space);
+ if (t != s+2) return 1; // scan failure
+ if (deg > 89) return 1; // too large value
+ s = t;
+ t = scan_int(s, 2, &m1, &seen_space);
+ if (t != s+2) return 1;
+ if (m1 > 59) return 1;
+ s = t;
+ c = *s;
+ if (!seen_space && c == '.') {
+ // OK
+ } else if (!seen_space && c == ' ') {
+ seen_space = 1;
+ } else {
+ return 1; // Bad char..
+ }
+ ++s;
+ t = scan_int(s, 2, &m2, &seen_space);
+ if (t != s+2) return 1;
+ s = t;
+ c = *s;
+ if (c != 'N' && c != 'n' && c != 'S' && c != 's') return 1;
+ }
+ return 0; /* zero for OK */
+}
+
+
+/*
+ * This interval parser is originally from ZMailer MTA.
+ * Slightly expanded to permit white-spaces inside the string.
+ * (c) Matti Aarnio, Rayan Zachariassen..
+ */
+
+static int parse_interval(const char *string, const char **restp)
+{
+ int intvl = 0;
+ int val;
+ int c;
+
+ for (; *string; ++string) {
+
+ val = 0;
+ c = *string;
+ while ('0' <= c && c <= '9') {
+ val = val * 10 + (c - '0');
+ c = *++string;
+ }
+
+ switch (c) {
+ case 'd': /* days */
+ case 'D': /* days */
+ val *= (24*60*60);
+ break;
+ case 'h': /* hours */
+ case 'H': /* hours */
+ val *= 60*60;
+ break;
+ case 'm': /* minutes */
+ case 'M': /* minutes */
+ val *= 60;
+ break;
+ case 's': /* seconds */
+ case 'S': /* seconds */
+ /* val *= 1; */
+ case '\t': /* just whitespace */
+ case ' ': /* just whitespace */
+ break;
+ default: /* Not of: "dhms" - maybe string end, maybe junk ? */
+ if (restp) *restp = string;
+ return intvl + val;
+ }
+ intvl += val;
+ }
+
+ if (restp) *restp = string;
+
+ return intvl;
+}
+
+// Return 0 on OK, != 0 on error
+int config_parse_interval(const char *par, int *resultp)
+{
+ const char *rest = NULL;
+ int ret = parse_interval(par, &rest);
+
+ if (*rest != 0) return 1; // Did not consume whole input string
+ *resultp = ret;
+ return 0;
+}
+
+// Return 0 on OK, != 0 on error
+int config_parse_boolean(const char *par, int *resultp)
+{
+ if (strcasecmp(par, "true") == 0 ||
+ strcmp(par, "1") == 0 ||
+ strcasecmp(par, "yes") == 0 ||
+ strcasecmp(par, "on") == 0 ||
+ strcasecmp(par, "y") == 0) {
+
+ *resultp = 1;
+ return 1;
+
+ } else if (strcasecmp(par, "false") == 0 ||
+ strcmp(par, "0") == 0 ||
+ strcasecmp(par, "no") == 0 ||
+ strcasecmp(par, "off") == 0 ||
+ strcasecmp(par, "n") == 0) {
+
+ *resultp = 0;
+ return 1;
+
+ } else {
+ return 0;
+ }
+}
+
+
+void *readconfigline(struct configfile *cf)
+{
+ char *bufp = cf->buf;
+ int buflen = sizeof(cf->buf);
+ //int llen;
+ cf->linenum = cf->linenum_i;
+ for (;;) {
+ char *p = fgets(bufp, buflen, cf->fp);
+ bufp[buflen - 1] = 0; /* Trunc, just in case.. */
+ if (p == NULL) {
+ if (bufp == cf->buf)
+ return NULL; // EOF!
+ return cf->buf; // Got EOF, but got also data before it!
+ }
+ cf->linenum_i += 1;
+ // Line ending LF ?
+ p = strchr(bufp, '\n');
+ if (p != NULL) {
+ *p-- = 0;
+ // Possible preceding CR ?
+ if (*p == '\r')
+ *p-- = 0;
+ // Line ending whitespaces ?
+ while (p > bufp && (*p == ' '||*p == '\t'))
+ *p-- = 0;
+ //llen = p - bufp;
+ }
+ if (p == NULL) {
+ p = bufp + strlen(bufp);
+ }
+ if (*p == '\\') {
+ bufp = p;
+ buflen = sizeof(cf->buf) - (p - cf->buf) -1;
+ continue;
+ } else {
+ // Not lone \ at line end. Not a line with continuation line..
+ break;
+ }
+ }
+
+ if (debug > 2)
+ printf("Config line: '%s'\n",cf->buf);
+
+ return cf->buf;
+}
+
+int configline_is_comment(struct configfile *cf)
+{
+ const char *buf = cf->buf;
+ const int buflen = sizeof(cf->buf);
+ char c = 0;
+ int i;
+
+ for (i = 0; buf[i] != 0 && i < buflen; ++i) {
+ c = buf[i];
+ if (c == ' ' || c == '\t')
+ continue;
+ /* Anything else, stop scanning */
+ break;
+ }
+ if (c == '#' || c == '\n' || c == '\r' || c == 0)
+ return 1;
+
+ return 0;
+}
+
+int readconfig(const char *name)
+{
+ struct configfile cf;
+ int has_fault = 0;
+ int i;
+
+ cf.linenum_i = 1;
+ cf.linenum = 1;
+ cf.name = name;
+
+ if ((cf.fp = fopen(name, "r")) == NULL) {
+ int e = errno;
+ printf("ERROR: Can not open named config file: '%s' -> %d %s\n",
+ name, e, strerror(e));
+ return 1;
+ }
+
+ while (readconfigline(&cf) != NULL) {
+ if (configline_is_comment(&cf))
+ continue; /* Comment line, or empty line */
+
+ i = cfgparam(&cf);
+ if (i) has_fault = 1;
+ }
+ fclose(cf.fp);
+
+ return has_fault;
+}
diff --git a/config.h.in b/config.h.in
new file mode 100644
index 0000000..39218d7
--- /dev/null
+++ b/config.h.in
@@ -0,0 +1,182 @@
+/* config.h.in. Generated from configure.in by autoheader. */
+
+/* Define if building universal (internal helper macro) */
+#undef AC_APPLE_UNIVERSAL_BUILD
+
+/* Configuration command line */
+#undef CONFIGURE_CMD
+
+/* Define to 1 if you want to disable all IGATE codes. */
+#undef DISABLE_IGATE
+
+/* Define for pthread(3p) disabling */
+#undef DISABLE_PTHREAD
+
+/* Define for an embedded target */
+#undef EMBEDDED
+
+/* Define to 1 if you want to enable AGWPE socket interface. */
+#undef ENABLE_AGWPE
+
+/* Define for pthread(3p) enabling */
+#undef ENABLE_PTHREAD
+
+/* Define for a non-embedded system with filesystem based Erlang history
+ storage */
+#undef ERLANGSTORAGE
+
+/* Define to 1 if you have the <alloca.h> header file. */
+#undef HAVE_ALLOCA_H
+
+/* Define to 1 if you have the `atan2f' function. */
+#undef HAVE_ATAN2F
+
+/* Have clock_gettime */
+#undef HAVE_CLOCK_GETTIME
+
+/* Define to 1 if you don't have `vprintf' but do have `_doprnt.' */
+#undef HAVE_DOPRNT
+
+/* Define to 1 if you have the `getaddrinfo' function. */
+#undef HAVE_GETADDRINFO
+
+/* Define to 1 if you have the `gettimeofday' function. */
+#undef HAVE_GETTIMEOFDAY
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#undef HAVE_INTTYPES_H
+
+/* Define to 1 if you have the `memchr' function. */
+#undef HAVE_MEMCHR
+
+/* Define to 1 if you have the <memory.h> header file. */
+#undef HAVE_MEMORY_H
+
+/* Define to 1 if you have the `memrchr' function. */
+#undef HAVE_MEMRCHR
+
+/* Define to 1 if you have the <netinet/sctp.h> header file. */
+#undef HAVE_NETINET_SCTP_H
+
+/* Define to 1 if you have the `openpty' function. */
+#undef HAVE_OPENPTY
+
+/* Define to 1 if you have the <openssl/ssl.h> header file. */
+#undef HAVE_OPENSSL_SSL_H
+
+/* Define to 1 if you have the <poll.h> header file. */
+#undef HAVE_POLL_H
+
+/* Have pthread_create() function */
+#undef HAVE_PTHREAD_CREATE
+
+/* Define to 1 if you have the <pthread.h> header file. */
+#undef HAVE_PTHREAD_H
+
+/* Define to 1 if you have the <pty.h> header file. */
+#undef HAVE_PTY_H
+
+/* Define to 1 if you have the `socket' function. */
+#undef HAVE_SOCKET
+
+/* Define to 1 if you have the `socketpair' function. */
+#undef HAVE_SOCKETPAIR
+
+/* Define to 1 if you have the <stdarg.h> header file. */
+#undef HAVE_STDARG_H
+
+/* Define to 1 if you have the <stddef.h> header file. */
+#undef HAVE_STDDEF_H
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#undef HAVE_STDINT_H
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#undef HAVE_STDLIB_H
+
+/* Define to 1 if you have the <strings.h> header file. */
+#undef HAVE_STRINGS_H
+
+/* Define to 1 if you have the <string.h> header file. */
+#undef HAVE_STRING_H
+
+/* Define to 1 if you have the <sys/epoll.h> header file. */
+#undef HAVE_SYS_EPOLL_H
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#undef HAVE_SYS_STAT_H
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#undef HAVE_SYS_TIME_H
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#undef HAVE_SYS_TYPES_H
+
+/* Define to 1 if you have the <sys/wait.h> header file. */
+#undef HAVE_SYS_WAIT_H
+
+/* Define to 1 if you have the <time.h> header file. */
+#undef HAVE_TIME_H
+
+/* OpenSSL 0.9.7 or later */
+#undef HAVE_TLSV1_SERVER_METHOD
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#undef HAVE_UNISTD_H
+
+/* Define to 1 if you have the <varargs.h> header file. */
+#undef HAVE_VARARGS_H
+
+/* Define to 1 if you have the `vprintf' function. */
+#undef HAVE_VPRINTF
+
+/* OpenSSL 0.9.7 or later */
+#undef HAVE_X509_FREE
+
+/* Define to the address where bug reports for this package should be sent. */
+#undef PACKAGE_BUGREPORT
+
+/* Define to the full name of this package. */
+#undef PACKAGE_NAME
+
+/* Define to the full name and version of this package. */
+#undef PACKAGE_STRING
+
+/* Define to the one symbol short name of this package. */
+#undef PACKAGE_TARNAME
+
+/* Define to the home page for this package. */
+#undef PACKAGE_URL
+
+/* Define to the version of this package. */
+#undef PACKAGE_VERSION
+
+/* The size of `double', as computed by sizeof. */
+#undef SIZEOF_DOUBLE
+
+/* The size of `int', as computed by sizeof. */
+#undef SIZEOF_INT
+
+/* The size of `long', as computed by sizeof. */
+#undef SIZEOF_LONG
+
+/* The size of `short', as computed by sizeof. */
+#undef SIZEOF_SHORT
+
+/* The size of `void *', as computed by sizeof. */
+#undef SIZEOF_VOID_P
+
+/* Define to 1 if you have the ANSI C header files. */
+#undef STDC_HEADERS
+
+/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most
+ significant byte first (like Motorola and SPARC, unlike Intel). */
+#if defined AC_APPLE_UNIVERSAL_BUILD
+# if defined __BIG_ENDIAN__
+# define WORDS_BIGENDIAN 1
+# endif
+#else
+# ifndef WORDS_BIGENDIAN
+# undef WORDS_BIGENDIAN
+# endif
+#endif
diff --git a/configure b/configure
new file mode 100755
index 0000000..cccfc02
--- /dev/null
+++ b/configure
@@ -0,0 +1,6245 @@
+#! /bin/sh
+# Guess values for system-dependent variables and create Makefiles.
+# Generated by GNU Autoconf 2.69.
+#
+#
+# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
+#
+#
+# This configure script is free software; the Free Software Foundation
+# gives unlimited permission to copy, distribute and modify it.
+## -------------------- ##
+## M4sh Initialization. ##
+## -------------------- ##
+
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
+ emulate sh
+ NULLCMD=:
+ # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+ setopt NO_GLOB_SUBST
+else
+ case `(set -o) 2>/dev/null` in #(
+ *posix*) :
+ set -o posix ;; #(
+ *) :
+ ;;
+esac
+fi
+
+
+as_nl='
+'
+export as_nl
+# Printing a long string crashes Solaris 7 /usr/bin/printf.
+as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
+# Prefer a ksh shell builtin over an external printf program on Solaris,
+# but without wasting forks for bash or zsh.
+if test -z "$BASH_VERSION$ZSH_VERSION" \
+ && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
+ as_echo='print -r --'
+ as_echo_n='print -rn --'
+elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
+ as_echo='printf %s\n'
+ as_echo_n='printf %s'
+else
+ if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
+ as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
+ as_echo_n='/usr/ucb/echo -n'
+ else
+ as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
+ as_echo_n_body='eval
+ arg=$1;
+ case $arg in #(
+ *"$as_nl"*)
+ expr "X$arg" : "X\\(.*\\)$as_nl";
+ arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
+ esac;
+ expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
+ '
+ export as_echo_n_body
+ as_echo_n='sh -c $as_echo_n_body as_echo'
+ fi
+ export as_echo_body
+ as_echo='sh -c $as_echo_body as_echo'
+fi
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+ PATH_SEPARATOR=:
+ (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
+ (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
+ PATH_SEPARATOR=';'
+ }
+fi
+
+
+# IFS
+# We need space, tab and new line, in precisely that order. Quoting is
+# there to prevent editors from complaining about space-tab.
+# (If _AS_PATH_WALK were called with IFS unset, it would disable word
+# splitting by setting IFS to empty value.)
+IFS=" "" $as_nl"
+
+# Find who we are. Look in the path if we contain no directory separator.
+as_myself=
+case $0 in #((
+ *[\\/]* ) as_myself=$0 ;;
+ *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+ as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+ $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+ exit 1
+fi
+
+# Unset variables that we do not need and which cause bugs (e.g. in
+# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1"
+# suppresses any "Segmentation fault" message there. '((' could
+# trigger a bug in pdksh 5.2.14.
+for as_var in BASH_ENV ENV MAIL MAILPATH
+do eval test x\${$as_var+set} = xset \
+ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
+done
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+LC_ALL=C
+export LC_ALL
+LANGUAGE=C
+export LANGUAGE
+
+# CDPATH.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+# Use a proper internal environment variable to ensure we don't fall
+ # into an infinite loop, continuously re-executing ourselves.
+ if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then
+ _as_can_reexec=no; export _as_can_reexec;
+ # We cannot yet assume a decent shell, so we have to provide a
+# neutralization value for shells without unset; and this also
+# works around shells that cannot unset nonexistent variables.
+# Preserve -v and -x to the replacement shell.
+BASH_ENV=/dev/null
+ENV=/dev/null
+(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
+case $- in # ((((
+ *v*x* | *x*v* ) as_opts=-vx ;;
+ *v* ) as_opts=-v ;;
+ *x* ) as_opts=-x ;;
+ * ) as_opts= ;;
+esac
+exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"}
+# Admittedly, this is quite paranoid, since all the known shells bail
+# out after a failed `exec'.
+$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2
+as_fn_exit 255
+ fi
+ # We don't want this to propagate to other subprocesses.
+ { _as_can_reexec=; unset _as_can_reexec;}
+if test "x$CONFIG_SHELL" = x; then
+ as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then :
+ emulate sh
+ NULLCMD=:
+ # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '\${1+\"\$@\"}'='\"\$@\"'
+ setopt NO_GLOB_SUBST
+else
+ case \`(set -o) 2>/dev/null\` in #(
+ *posix*) :
+ set -o posix ;; #(
+ *) :
+ ;;
+esac
+fi
+"
+ as_required="as_fn_return () { (exit \$1); }
+as_fn_success () { as_fn_return 0; }
+as_fn_failure () { as_fn_return 1; }
+as_fn_ret_success () { return 0; }
+as_fn_ret_failure () { return 1; }
+
+exitcode=0
+as_fn_success || { exitcode=1; echo as_fn_success failed.; }
+as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; }
+as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; }
+as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; }
+if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then :
+
+else
+ exitcode=1; echo positional parameters were not saved.
+fi
+test x\$exitcode = x0 || exit 1
+test -x / || exit 1"
+ as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO
+ as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO
+ eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" &&
+ test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1
+test \$(( 1 + 1 )) = 2 || exit 1"
+ if (eval "$as_required") 2>/dev/null; then :
+ as_have_required=yes
+else
+ as_have_required=no
+fi
+ if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then :
+
+else
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+as_found=false
+for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ as_found=:
+ case $as_dir in #(
+ /*)
+ for as_base in sh bash ksh sh5; do
+ # Try only shells that exist, to save several forks.
+ as_shell=$as_dir/$as_base
+ if { test -f "$as_shell" || test -f "$as_shell.exe"; } &&
+ { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then :
+ CONFIG_SHELL=$as_shell as_have_required=yes
+ if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then :
+ break 2
+fi
+fi
+ done;;
+ esac
+ as_found=false
+done
+$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } &&
+ { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then :
+ CONFIG_SHELL=$SHELL as_have_required=yes
+fi; }
+IFS=$as_save_IFS
+
+
+ if test "x$CONFIG_SHELL" != x; then :
+ export CONFIG_SHELL
+ # We cannot yet assume a decent shell, so we have to provide a
+# neutralization value for shells without unset; and this also
+# works around shells that cannot unset nonexistent variables.
+# Preserve -v and -x to the replacement shell.
+BASH_ENV=/dev/null
+ENV=/dev/null
+(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
+case $- in # ((((
+ *v*x* | *x*v* ) as_opts=-vx ;;
+ *v* ) as_opts=-v ;;
+ *x* ) as_opts=-x ;;
+ * ) as_opts= ;;
+esac
+exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"}
+# Admittedly, this is quite paranoid, since all the known shells bail
+# out after a failed `exec'.
+$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2
+exit 255
+fi
+
+ if test x$as_have_required = xno; then :
+ $as_echo "$0: This script requires a shell more modern than all"
+ $as_echo "$0: the shells that I found on your system."
+ if test x${ZSH_VERSION+set} = xset ; then
+ $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should"
+ $as_echo "$0: be upgraded to zsh 4.3.4 or later."
+ else
+ $as_echo "$0: Please tell bug-autoconf at gnu.org about your system,
+$0: including any error possibly output before this
+$0: message. Then install a modern shell, or manually run
+$0: the script under such a shell if you do have one."
+ fi
+ exit 1
+fi
+fi
+fi
+SHELL=${CONFIG_SHELL-/bin/sh}
+export SHELL
+# Unset more variables known to interfere with behavior of common tools.
+CLICOLOR_FORCE= GREP_OPTIONS=
+unset CLICOLOR_FORCE GREP_OPTIONS
+
+## --------------------- ##
+## M4sh Shell Functions. ##
+## --------------------- ##
+# as_fn_unset VAR
+# ---------------
+# Portably unset VAR.
+as_fn_unset ()
+{
+ { eval $1=; unset $1;}
+}
+as_unset=as_fn_unset
+
+# as_fn_set_status STATUS
+# -----------------------
+# Set $? to STATUS, without forking.
+as_fn_set_status ()
+{
+ return $1
+} # as_fn_set_status
+
+# as_fn_exit STATUS
+# -----------------
+# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
+as_fn_exit ()
+{
+ set +e
+ as_fn_set_status $1
+ exit $1
+} # as_fn_exit
+
+# as_fn_mkdir_p
+# -------------
+# Create "$as_dir" as a directory, including parents if necessary.
+as_fn_mkdir_p ()
+{
+
+ case $as_dir in #(
+ -*) as_dir=./$as_dir;;
+ esac
+ test -d "$as_dir" || eval $as_mkdir_p || {
+ as_dirs=
+ while :; do
+ case $as_dir in #(
+ *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
+ *) as_qdir=$as_dir;;
+ esac
+ as_dirs="'$as_qdir' $as_dirs"
+ as_dir=`$as_dirname -- "$as_dir" ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$as_dir" : 'X\(//\)[^/]' \| \
+ X"$as_dir" : 'X\(//\)$' \| \
+ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_dir" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)[^/].*/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+ test -d "$as_dir" && break
+ done
+ test -z "$as_dirs" || eval "mkdir $as_dirs"
+ } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
+
+
+} # as_fn_mkdir_p
+
+# as_fn_executable_p FILE
+# -----------------------
+# Test if FILE is an executable regular file.
+as_fn_executable_p ()
+{
+ test -f "$1" && test -x "$1"
+} # as_fn_executable_p
+# as_fn_append VAR VALUE
+# ----------------------
+# Append the text in VALUE to the end of the definition contained in VAR. Take
+# advantage of any shell optimizations that allow amortized linear growth over
+# repeated appends, instead of the typical quadratic growth present in naive
+# implementations.
+if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
+ eval 'as_fn_append ()
+ {
+ eval $1+=\$2
+ }'
+else
+ as_fn_append ()
+ {
+ eval $1=\$$1\$2
+ }
+fi # as_fn_append
+
+# as_fn_arith ARG...
+# ------------------
+# Perform arithmetic evaluation on the ARGs, and store the result in the
+# global $as_val. Take advantage of shells that can avoid forks. The arguments
+# must be portable across $(()) and expr.
+if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
+ eval 'as_fn_arith ()
+ {
+ as_val=$(( $* ))
+ }'
+else
+ as_fn_arith ()
+ {
+ as_val=`expr "$@" || test $? -eq 1`
+ }
+fi # as_fn_arith
+
+
+# as_fn_error STATUS ERROR [LINENO LOG_FD]
+# ----------------------------------------
+# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
+# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
+# script with STATUS, using 1 if that was 0.
+as_fn_error ()
+{
+ as_status=$1; test $as_status -eq 0 && as_status=1
+ if test "$4"; then
+ as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
+ fi
+ $as_echo "$as_me: error: $2" >&2
+ as_fn_exit $as_status
+} # as_fn_error
+
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+ test "X`expr 00001 : '.*\(...\)'`" = X001; then
+ as_expr=expr
+else
+ as_expr=false
+fi
+
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
+ as_basename=basename
+else
+ as_basename=false
+fi
+
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+ as_dirname=dirname
+else
+ as_dirname=false
+fi
+
+as_me=`$as_basename -- "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+ X"$0" : 'X\(//\)$' \| \
+ X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X/"$0" |
+ sed '/^.*\/\([^/][^/]*\)\/*$/{
+ s//\1/
+ q
+ }
+ /^X\/\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\/\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+
+ as_lineno_1=$LINENO as_lineno_1a=$LINENO
+ as_lineno_2=$LINENO as_lineno_2a=$LINENO
+ eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" &&
+ test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || {
+ # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-)
+ sed -n '
+ p
+ /[$]LINENO/=
+ ' <$as_myself |
+ sed '
+ s/[$]LINENO.*/&-/
+ t lineno
+ b
+ :lineno
+ N
+ :loop
+ s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/
+ t loop
+ s/-\n.*//
+ ' >$as_me.lineno &&
+ chmod +x "$as_me.lineno" ||
+ { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; }
+
+ # If we had to re-execute with $CONFIG_SHELL, we're ensured to have
+ # already done that, so ensure we don't try to do so again and fall
+ # in an infinite loop. This has already happened in practice.
+ _as_can_reexec=no; export _as_can_reexec
+ # Don't try to exec as it changes $[0], causing all sort of problems
+ # (the dirname of $[0] is not the place where we might find the
+ # original and so on. Autoconf is especially sensitive to this).
+ . "./$as_me.lineno"
+ # Exit status is that of the last command.
+ exit
+}
+
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in #(((((
+-n*)
+ case `echo 'xy\c'` in
+ *c*) ECHO_T=' ';; # ECHO_T is single tab character.
+ xy) ECHO_C='\c';;
+ *) echo `echo ksh88 bug on AIX 6.1` > /dev/null
+ ECHO_T=' ';;
+ esac;;
+*)
+ ECHO_N='-n';;
+esac
+
+rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+ rm -f conf$$.dir/conf$$.file
+else
+ rm -f conf$$.dir
+ mkdir conf$$.dir 2>/dev/null
+fi
+if (echo >conf$$.file) 2>/dev/null; then
+ if ln -s conf$$.file conf$$ 2>/dev/null; then
+ as_ln_s='ln -s'
+ # ... but there are two gotchas:
+ # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+ # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+ # In both cases, we have to default to `cp -pR'.
+ ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
+ as_ln_s='cp -pR'
+ elif ln conf$$.file conf$$ 2>/dev/null; then
+ as_ln_s=ln
+ else
+ as_ln_s='cp -pR'
+ fi
+else
+ as_ln_s='cp -pR'
+fi
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
+
+if mkdir -p . 2>/dev/null; then
+ as_mkdir_p='mkdir -p "$as_dir"'
+else
+ test -d ./-p && rmdir ./-p
+ as_mkdir_p=false
+fi
+
+as_test_x='test -x'
+as_executable_p=as_fn_executable_p
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+test -n "$DJDIR" || exec 7<&0 </dev/null
+exec 6>&1
+
+# Name of the host.
+# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status,
+# so uname gets run too.
+ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q`
+
+#
+# Initializations.
+#
+ac_default_prefix=/usr/local
+ac_clean_files=
+ac_config_libobj_dir=.
+LIBOBJS=
+cross_compiling=no
+subdirs=
+MFLAGS=
+MAKEFLAGS=
+
+# Identity of this package.
+PACKAGE_NAME=
+PACKAGE_TARNAME=
+PACKAGE_VERSION=
+PACKAGE_STRING=
+PACKAGE_BUGREPORT=
+PACKAGE_URL=
+
+ac_unique_file="aprx.h"
+# Factoring default headers for most tests.
+ac_includes_default="\
+#include <stdio.h>
+#ifdef HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+# include <stdlib.h>
+# endif
+#endif
+#ifdef HAVE_STRING_H
+# if !defined STDC_HEADERS && defined HAVE_MEMORY_H
+# include <memory.h>
+# endif
+# include <string.h>
+#endif
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif
+#ifdef HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif"
+
+ac_subst_vars='LTLIBOBJS
+LIBOBJS
+SVNVERSION_STRING
+VERSION_STRING
+LIBCRYPTO
+LIBSSL
+LIBRESOLV
+LIBSOCKET
+LIBGETADDRINFO
+LIBRT
+CCPTHREAD
+LIBPTHREAD
+LIBM
+CFLAGS_ARCH
+LD
+EGREP
+GREP
+CPP
+OBJEXT
+EXEEXT
+ac_ct_CC
+CPPFLAGS
+LDFLAGS
+CFLAGS
+CC
+SET_MAKE
+target_alias
+host_alias
+build_alias
+LIBS
+ECHO_T
+ECHO_N
+ECHO_C
+DEFS
+mandir
+localedir
+libdir
+psdir
+pdfdir
+dvidir
+htmldir
+infodir
+docdir
+oldincludedir
+includedir
+localstatedir
+sharedstatedir
+sysconfdir
+datadir
+datarootdir
+libexecdir
+sbindir
+bindir
+program_transform_name
+prefix
+exec_prefix
+PACKAGE_URL
+PACKAGE_BUGREPORT
+PACKAGE_STRING
+PACKAGE_VERSION
+PACKAGE_TARNAME
+PACKAGE_NAME
+PATH_SEPARATOR
+SHELL'
+ac_subst_files=''
+ac_user_opts='
+enable_option_checking
+with_embedded
+with_erlangstorage
+enable_igate
+enable_agwpe
+with_pthread
+with_pthreads
+with_openssl
+'
+ ac_precious_vars='build_alias
+host_alias
+target_alias
+CC
+CFLAGS
+LDFLAGS
+LIBS
+CPPFLAGS
+CPP'
+
+
+# Initialize some variables set by options.
+ac_init_help=
+ac_init_version=false
+ac_unrecognized_opts=
+ac_unrecognized_sep=
+# The variables have the same names as the options, with
+# dashes changed to underlines.
+cache_file=/dev/null
+exec_prefix=NONE
+no_create=
+no_recursion=
+prefix=NONE
+program_prefix=NONE
+program_suffix=NONE
+program_transform_name=s,x,x,
+silent=
+site=
+srcdir=
+verbose=
+x_includes=NONE
+x_libraries=NONE
+
+# Installation directory options.
+# These are left unexpanded so users can "make install exec_prefix=/foo"
+# and all the variables that are supposed to be based on exec_prefix
+# by default will actually change.
+# Use braces instead of parens because sh, perl, etc. also accept them.
+# (The list follows the same order as the GNU Coding Standards.)
+bindir='${exec_prefix}/bin'
+sbindir='${exec_prefix}/sbin'
+libexecdir='${exec_prefix}/libexec'
+datarootdir='${prefix}/share'
+datadir='${datarootdir}'
+sysconfdir='${prefix}/etc'
+sharedstatedir='${prefix}/com'
+localstatedir='${prefix}/var'
+includedir='${prefix}/include'
+oldincludedir='/usr/include'
+docdir='${datarootdir}/doc/${PACKAGE}'
+infodir='${datarootdir}/info'
+htmldir='${docdir}'
+dvidir='${docdir}'
+pdfdir='${docdir}'
+psdir='${docdir}'
+libdir='${exec_prefix}/lib'
+localedir='${datarootdir}/locale'
+mandir='${datarootdir}/man'
+
+ac_prev=
+ac_dashdash=
+for ac_option
+do
+ # If the previous option needs an argument, assign it.
+ if test -n "$ac_prev"; then
+ eval $ac_prev=\$ac_option
+ ac_prev=
+ continue
+ fi
+
+ case $ac_option in
+ *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;;
+ *=) ac_optarg= ;;
+ *) ac_optarg=yes ;;
+ esac
+
+ # Accept the important Cygnus configure options, so we can diagnose typos.
+
+ case $ac_dashdash$ac_option in
+ --)
+ ac_dashdash=yes ;;
+
+ -bindir | --bindir | --bindi | --bind | --bin | --bi)
+ ac_prev=bindir ;;
+ -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*)
+ bindir=$ac_optarg ;;
+
+ -build | --build | --buil | --bui | --bu)
+ ac_prev=build_alias ;;
+ -build=* | --build=* | --buil=* | --bui=* | --bu=*)
+ build_alias=$ac_optarg ;;
+
+ -cache-file | --cache-file | --cache-fil | --cache-fi \
+ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+ ac_prev=cache_file ;;
+ -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*)
+ cache_file=$ac_optarg ;;
+
+ --config-cache | -C)
+ cache_file=config.cache ;;
+
+ -datadir | --datadir | --datadi | --datad)
+ ac_prev=datadir ;;
+ -datadir=* | --datadir=* | --datadi=* | --datad=*)
+ datadir=$ac_optarg ;;
+
+ -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \
+ | --dataroo | --dataro | --datar)
+ ac_prev=datarootdir ;;
+ -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \
+ | --dataroot=* | --dataroo=* | --dataro=* | --datar=*)
+ datarootdir=$ac_optarg ;;
+
+ -disable-* | --disable-*)
+ ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+ as_fn_error $? "invalid feature name: $ac_useropt"
+ ac_useropt_orig=$ac_useropt
+ ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+ case $ac_user_opts in
+ *"
+"enable_$ac_useropt"
+"*) ;;
+ *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig"
+ ac_unrecognized_sep=', ';;
+ esac
+ eval enable_$ac_useropt=no ;;
+
+ -docdir | --docdir | --docdi | --doc | --do)
+ ac_prev=docdir ;;
+ -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*)
+ docdir=$ac_optarg ;;
+
+ -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv)
+ ac_prev=dvidir ;;
+ -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*)
+ dvidir=$ac_optarg ;;
+
+ -enable-* | --enable-*)
+ ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+ as_fn_error $? "invalid feature name: $ac_useropt"
+ ac_useropt_orig=$ac_useropt
+ ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+ case $ac_user_opts in
+ *"
+"enable_$ac_useropt"
+"*) ;;
+ *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig"
+ ac_unrecognized_sep=', ';;
+ esac
+ eval enable_$ac_useropt=\$ac_optarg ;;
+
+ -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
+ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
+ | --exec | --exe | --ex)
+ ac_prev=exec_prefix ;;
+ -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \
+ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \
+ | --exec=* | --exe=* | --ex=*)
+ exec_prefix=$ac_optarg ;;
+
+ -gas | --gas | --ga | --g)
+ # Obsolete; use --with-gas.
+ with_gas=yes ;;
+
+ -help | --help | --hel | --he | -h)
+ ac_init_help=long ;;
+ -help=r* | --help=r* | --hel=r* | --he=r* | -hr*)
+ ac_init_help=recursive ;;
+ -help=s* | --help=s* | --hel=s* | --he=s* | -hs*)
+ ac_init_help=short ;;
+
+ -host | --host | --hos | --ho)
+ ac_prev=host_alias ;;
+ -host=* | --host=* | --hos=* | --ho=*)
+ host_alias=$ac_optarg ;;
+
+ -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht)
+ ac_prev=htmldir ;;
+ -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \
+ | --ht=*)
+ htmldir=$ac_optarg ;;
+
+ -includedir | --includedir | --includedi | --included | --include \
+ | --includ | --inclu | --incl | --inc)
+ ac_prev=includedir ;;
+ -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \
+ | --includ=* | --inclu=* | --incl=* | --inc=*)
+ includedir=$ac_optarg ;;
+
+ -infodir | --infodir | --infodi | --infod | --info | --inf)
+ ac_prev=infodir ;;
+ -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*)
+ infodir=$ac_optarg ;;
+
+ -libdir | --libdir | --libdi | --libd)
+ ac_prev=libdir ;;
+ -libdir=* | --libdir=* | --libdi=* | --libd=*)
+ libdir=$ac_optarg ;;
+
+ -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \
+ | --libexe | --libex | --libe)
+ ac_prev=libexecdir ;;
+ -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \
+ | --libexe=* | --libex=* | --libe=*)
+ libexecdir=$ac_optarg ;;
+
+ -localedir | --localedir | --localedi | --localed | --locale)
+ ac_prev=localedir ;;
+ -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*)
+ localedir=$ac_optarg ;;
+
+ -localstatedir | --localstatedir | --localstatedi | --localstated \
+ | --localstate | --localstat | --localsta | --localst | --locals)
+ ac_prev=localstatedir ;;
+ -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
+ | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*)
+ localstatedir=$ac_optarg ;;
+
+ -mandir | --mandir | --mandi | --mand | --man | --ma | --m)
+ ac_prev=mandir ;;
+ -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*)
+ mandir=$ac_optarg ;;
+
+ -nfp | --nfp | --nf)
+ # Obsolete; use --without-fp.
+ with_fp=no ;;
+
+ -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+ | --no-cr | --no-c | -n)
+ no_create=yes ;;
+
+ -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
+ no_recursion=yes ;;
+
+ -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \
+ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \
+ | --oldin | --oldi | --old | --ol | --o)
+ ac_prev=oldincludedir ;;
+ -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \
+ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \
+ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*)
+ oldincludedir=$ac_optarg ;;
+
+ -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+ ac_prev=prefix ;;
+ -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+ prefix=$ac_optarg ;;
+
+ -program-prefix | --program-prefix | --program-prefi | --program-pref \
+ | --program-pre | --program-pr | --program-p)
+ ac_prev=program_prefix ;;
+ -program-prefix=* | --program-prefix=* | --program-prefi=* \
+ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*)
+ program_prefix=$ac_optarg ;;
+
+ -program-suffix | --program-suffix | --program-suffi | --program-suff \
+ | --program-suf | --program-su | --program-s)
+ ac_prev=program_suffix ;;
+ -program-suffix=* | --program-suffix=* | --program-suffi=* \
+ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*)
+ program_suffix=$ac_optarg ;;
+
+ -program-transform-name | --program-transform-name \
+ | --program-transform-nam | --program-transform-na \
+ | --program-transform-n | --program-transform- \
+ | --program-transform | --program-transfor \
+ | --program-transfo | --program-transf \
+ | --program-trans | --program-tran \
+ | --progr-tra | --program-tr | --program-t)
+ ac_prev=program_transform_name ;;
+ -program-transform-name=* | --program-transform-name=* \
+ | --program-transform-nam=* | --program-transform-na=* \
+ | --program-transform-n=* | --program-transform-=* \
+ | --program-transform=* | --program-transfor=* \
+ | --program-transfo=* | --program-transf=* \
+ | --program-trans=* | --program-tran=* \
+ | --progr-tra=* | --program-tr=* | --program-t=*)
+ program_transform_name=$ac_optarg ;;
+
+ -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd)
+ ac_prev=pdfdir ;;
+ -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*)
+ pdfdir=$ac_optarg ;;
+
+ -psdir | --psdir | --psdi | --psd | --ps)
+ ac_prev=psdir ;;
+ -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*)
+ psdir=$ac_optarg ;;
+
+ -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+ | -silent | --silent | --silen | --sile | --sil)
+ silent=yes ;;
+
+ -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
+ ac_prev=sbindir ;;
+ -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
+ | --sbi=* | --sb=*)
+ sbindir=$ac_optarg ;;
+
+ -sharedstatedir | --sharedstatedir | --sharedstatedi \
+ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \
+ | --sharedst | --shareds | --shared | --share | --shar \
+ | --sha | --sh)
+ ac_prev=sharedstatedir ;;
+ -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \
+ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \
+ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \
+ | --sha=* | --sh=*)
+ sharedstatedir=$ac_optarg ;;
+
+ -site | --site | --sit)
+ ac_prev=site ;;
+ -site=* | --site=* | --sit=*)
+ site=$ac_optarg ;;
+
+ -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+ ac_prev=srcdir ;;
+ -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+ srcdir=$ac_optarg ;;
+
+ -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \
+ | --syscon | --sysco | --sysc | --sys | --sy)
+ ac_prev=sysconfdir ;;
+ -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \
+ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*)
+ sysconfdir=$ac_optarg ;;
+
+ -target | --target | --targe | --targ | --tar | --ta | --t)
+ ac_prev=target_alias ;;
+ -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*)
+ target_alias=$ac_optarg ;;
+
+ -v | -verbose | --verbose | --verbos | --verbo | --verb)
+ verbose=yes ;;
+
+ -version | --version | --versio | --versi | --vers | -V)
+ ac_init_version=: ;;
+
+ -with-* | --with-*)
+ ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+ as_fn_error $? "invalid package name: $ac_useropt"
+ ac_useropt_orig=$ac_useropt
+ ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+ case $ac_user_opts in
+ *"
+"with_$ac_useropt"
+"*) ;;
+ *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig"
+ ac_unrecognized_sep=', ';;
+ esac
+ eval with_$ac_useropt=\$ac_optarg ;;
+
+ -without-* | --without-*)
+ ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+ as_fn_error $? "invalid package name: $ac_useropt"
+ ac_useropt_orig=$ac_useropt
+ ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+ case $ac_user_opts in
+ *"
+"with_$ac_useropt"
+"*) ;;
+ *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig"
+ ac_unrecognized_sep=', ';;
+ esac
+ eval with_$ac_useropt=no ;;
+
+ --x)
+ # Obsolete; use --with-x.
+ with_x=yes ;;
+
+ -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \
+ | --x-incl | --x-inc | --x-in | --x-i)
+ ac_prev=x_includes ;;
+ -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \
+ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*)
+ x_includes=$ac_optarg ;;
+
+ -x-libraries | --x-libraries | --x-librarie | --x-librari \
+ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l)
+ ac_prev=x_libraries ;;
+ -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \
+ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
+ x_libraries=$ac_optarg ;;
+
+ -*) as_fn_error $? "unrecognized option: \`$ac_option'
+Try \`$0 --help' for more information"
+ ;;
+
+ *=*)
+ ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='`
+ # Reject names that are not valid shell variable names.
+ case $ac_envvar in #(
+ '' | [0-9]* | *[!_$as_cr_alnum]* )
+ as_fn_error $? "invalid variable name: \`$ac_envvar'" ;;
+ esac
+ eval $ac_envvar=\$ac_optarg
+ export $ac_envvar ;;
+
+ *)
+ # FIXME: should be removed in autoconf 3.0.
+ $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2
+ expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null &&
+ $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2
+ : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}"
+ ;;
+
+ esac
+done
+
+if test -n "$ac_prev"; then
+ ac_option=--`echo $ac_prev | sed 's/_/-/g'`
+ as_fn_error $? "missing argument to $ac_option"
+fi
+
+if test -n "$ac_unrecognized_opts"; then
+ case $enable_option_checking in
+ no) ;;
+ fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;;
+ *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;;
+ esac
+fi
+
+# Check all directory arguments for consistency.
+for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \
+ datadir sysconfdir sharedstatedir localstatedir includedir \
+ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
+ libdir localedir mandir
+do
+ eval ac_val=\$$ac_var
+ # Remove trailing slashes.
+ case $ac_val in
+ */ )
+ ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'`
+ eval $ac_var=\$ac_val;;
+ esac
+ # Be sure to have absolute directory names.
+ case $ac_val in
+ [\\/$]* | ?:[\\/]* ) continue;;
+ NONE | '' ) case $ac_var in *prefix ) continue;; esac;;
+ esac
+ as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val"
+done
+
+# There might be people who depend on the old broken behavior: `$host'
+# used to hold the argument of --host etc.
+# FIXME: To remove some day.
+build=$build_alias
+host=$host_alias
+target=$target_alias
+
+# FIXME: To remove some day.
+if test "x$host_alias" != x; then
+ if test "x$build_alias" = x; then
+ cross_compiling=maybe
+ elif test "x$build_alias" != "x$host_alias"; then
+ cross_compiling=yes
+ fi
+fi
+
+ac_tool_prefix=
+test -n "$host_alias" && ac_tool_prefix=$host_alias-
+
+test "$silent" = yes && exec 6>/dev/null
+
+
+ac_pwd=`pwd` && test -n "$ac_pwd" &&
+ac_ls_di=`ls -di .` &&
+ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` ||
+ as_fn_error $? "working directory cannot be determined"
+test "X$ac_ls_di" = "X$ac_pwd_ls_di" ||
+ as_fn_error $? "pwd does not report name of working directory"
+
+
+# Find the source files, if location was not specified.
+if test -z "$srcdir"; then
+ ac_srcdir_defaulted=yes
+ # Try the directory containing this script, then the parent directory.
+ ac_confdir=`$as_dirname -- "$as_myself" ||
+$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$as_myself" : 'X\(//\)[^/]' \| \
+ X"$as_myself" : 'X\(//\)$' \| \
+ X"$as_myself" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_myself" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)[^/].*/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+ srcdir=$ac_confdir
+ if test ! -r "$srcdir/$ac_unique_file"; then
+ srcdir=..
+ fi
+else
+ ac_srcdir_defaulted=no
+fi
+if test ! -r "$srcdir/$ac_unique_file"; then
+ test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .."
+ as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir"
+fi
+ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work"
+ac_abs_confdir=`(
+ cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg"
+ pwd)`
+# When building in place, set srcdir=.
+if test "$ac_abs_confdir" = "$ac_pwd"; then
+ srcdir=.
+fi
+# Remove unnecessary trailing slashes from srcdir.
+# Double slashes in file names in object file debugging info
+# mess up M-x gdb in Emacs.
+case $srcdir in
+*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;;
+esac
+for ac_var in $ac_precious_vars; do
+ eval ac_env_${ac_var}_set=\${${ac_var}+set}
+ eval ac_env_${ac_var}_value=\$${ac_var}
+ eval ac_cv_env_${ac_var}_set=\${${ac_var}+set}
+ eval ac_cv_env_${ac_var}_value=\$${ac_var}
+done
+
+#
+# Report the --help message.
+#
+if test "$ac_init_help" = "long"; then
+ # Omit some internal or obsolete options to make the list less imposing.
+ # This message is too long to be a string in the A/UX 3.1 sh.
+ cat <<_ACEOF
+\`configure' configures this package to adapt to many kinds of systems.
+
+Usage: $0 [OPTION]... [VAR=VALUE]...
+
+To assign environment variables (e.g., CC, CFLAGS...), specify them as
+VAR=VALUE. See below for descriptions of some of the useful variables.
+
+Defaults for the options are specified in brackets.
+
+Configuration:
+ -h, --help display this help and exit
+ --help=short display options specific to this package
+ --help=recursive display the short help of all the included packages
+ -V, --version display version information and exit
+ -q, --quiet, --silent do not print \`checking ...' messages
+ --cache-file=FILE cache test results in FILE [disabled]
+ -C, --config-cache alias for \`--cache-file=config.cache'
+ -n, --no-create do not create output files
+ --srcdir=DIR find the sources in DIR [configure dir or \`..']
+
+Installation directories:
+ --prefix=PREFIX install architecture-independent files in PREFIX
+ [$ac_default_prefix]
+ --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX
+ [PREFIX]
+
+By default, \`make install' will install all the files in
+\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify
+an installation prefix other than \`$ac_default_prefix' using \`--prefix',
+for instance \`--prefix=\$HOME'.
+
+For better control, use the options below.
+
+Fine tuning of the installation directories:
+ --bindir=DIR user executables [EPREFIX/bin]
+ --sbindir=DIR system admin executables [EPREFIX/sbin]
+ --libexecdir=DIR program executables [EPREFIX/libexec]
+ --sysconfdir=DIR read-only single-machine data [PREFIX/etc]
+ --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
+ --localstatedir=DIR modifiable single-machine data [PREFIX/var]
+ --libdir=DIR object code libraries [EPREFIX/lib]
+ --includedir=DIR C header files [PREFIX/include]
+ --oldincludedir=DIR C header files for non-gcc [/usr/include]
+ --datarootdir=DIR read-only arch.-independent data root [PREFIX/share]
+ --datadir=DIR read-only architecture-independent data [DATAROOTDIR]
+ --infodir=DIR info documentation [DATAROOTDIR/info]
+ --localedir=DIR locale-dependent data [DATAROOTDIR/locale]
+ --mandir=DIR man documentation [DATAROOTDIR/man]
+ --docdir=DIR documentation root [DATAROOTDIR/doc/PACKAGE]
+ --htmldir=DIR html documentation [DOCDIR]
+ --dvidir=DIR dvi documentation [DOCDIR]
+ --pdfdir=DIR pdf documentation [DOCDIR]
+ --psdir=DIR ps documentation [DOCDIR]
+_ACEOF
+
+ cat <<\_ACEOF
+_ACEOF
+fi
+
+if test -n "$ac_init_help"; then
+
+ cat <<\_ACEOF
+
+Optional Features:
+ --disable-option-checking ignore unrecognized --enable/--with options
+ --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
+ --enable-FEATURE[=ARG] include FEATURE [ARG=yes]
+ --disable-igate Disable all IGate codes
+ --enable-agwpe Enable AGWPE socket interface code.
+
+Optional Packages:
+ --with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
+ --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
+ --with-embedded When desiring to target as embedded
+ --with-erlangstorage When desiring a longer term backing storage on erlang datasets. NOT compatible with EMBEDDED, REQUIRES FILESYSTEM!
+ --without-pthread When desiring not to use pthread subsystem
+ --with-pthreads (mistyped pthread) When desiring use pthread subsystem
+ --with-openssl=DIR Include OpenSSL support (requires OpenSSL >= 0.9.7)
+
+Some influential environment variables:
+ CC C compiler command
+ CFLAGS C compiler flags
+ LDFLAGS linker flags, e.g. -L<lib dir> if you have libraries in a
+ nonstandard directory <lib dir>
+ LIBS libraries to pass to the linker, e.g. -l<library>
+ CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if
+ you have headers in a nonstandard directory <include dir>
+ CPP C preprocessor
+
+Use these variables to override the choices made by `configure' or to help
+it to find libraries and programs with nonstandard names/locations.
+
+Report bugs to the package provider.
+_ACEOF
+ac_status=$?
+fi
+
+if test "$ac_init_help" = "recursive"; then
+ # If there are subdirs, report their specific --help.
+ for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue
+ test -d "$ac_dir" ||
+ { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } ||
+ continue
+ ac_builddir=.
+
+case "$ac_dir" in
+.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
+*)
+ ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
+ # A ".." for each directory in $ac_dir_suffix.
+ ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
+ case $ac_top_builddir_sub in
+ "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
+ *) ac_top_build_prefix=$ac_top_builddir_sub/ ;;
+ esac ;;
+esac
+ac_abs_top_builddir=$ac_pwd
+ac_abs_builddir=$ac_pwd$ac_dir_suffix
+# for backward compatibility:
+ac_top_builddir=$ac_top_build_prefix
+
+case $srcdir in
+ .) # We are building in place.
+ ac_srcdir=.
+ ac_top_srcdir=$ac_top_builddir_sub
+ ac_abs_top_srcdir=$ac_pwd ;;
+ [\\/]* | ?:[\\/]* ) # Absolute name.
+ ac_srcdir=$srcdir$ac_dir_suffix;
+ ac_top_srcdir=$srcdir
+ ac_abs_top_srcdir=$srcdir ;;
+ *) # Relative name.
+ ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
+ ac_top_srcdir=$ac_top_build_prefix$srcdir
+ ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
+esac
+ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
+
+ cd "$ac_dir" || { ac_status=$?; continue; }
+ # Check for guested configure.
+ if test -f "$ac_srcdir/configure.gnu"; then
+ echo &&
+ $SHELL "$ac_srcdir/configure.gnu" --help=recursive
+ elif test -f "$ac_srcdir/configure"; then
+ echo &&
+ $SHELL "$ac_srcdir/configure" --help=recursive
+ else
+ $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2
+ fi || ac_status=$?
+ cd "$ac_pwd" || { ac_status=$?; break; }
+ done
+fi
+
+test -n "$ac_init_help" && exit $ac_status
+if $ac_init_version; then
+ cat <<\_ACEOF
+configure
+generated by GNU Autoconf 2.69
+
+Copyright (C) 2012 Free Software Foundation, Inc.
+This configure script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it.
+_ACEOF
+ exit
+fi
+
+## ------------------------ ##
+## Autoconf initialization. ##
+## ------------------------ ##
+
+# ac_fn_c_try_compile LINENO
+# --------------------------
+# Try to compile conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_compile ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ rm -f conftest.$ac_objext
+ if { { ac_try="$ac_compile"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_compile") 2>conftest.err
+ ac_status=$?
+ if test -s conftest.err; then
+ grep -v '^ *+' conftest.err >conftest.er1
+ cat conftest.er1 >&5
+ mv -f conftest.er1 conftest.err
+ fi
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } && {
+ test -z "$ac_c_werror_flag" ||
+ test ! -s conftest.err
+ } && test -s conftest.$ac_objext; then :
+ ac_retval=0
+else
+ $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ ac_retval=1
+fi
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+ as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_compile
+
+# ac_fn_c_try_cpp LINENO
+# ----------------------
+# Try to preprocess conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_cpp ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ if { { ac_try="$ac_cpp conftest.$ac_ext"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err
+ ac_status=$?
+ if test -s conftest.err; then
+ grep -v '^ *+' conftest.err >conftest.er1
+ cat conftest.er1 >&5
+ mv -f conftest.er1 conftest.err
+ fi
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } > conftest.i && {
+ test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" ||
+ test ! -s conftest.err
+ }; then :
+ ac_retval=0
+else
+ $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ ac_retval=1
+fi
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+ as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_cpp
+
+# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES
+# -------------------------------------------------------
+# Tests whether HEADER exists, giving a warning if it cannot be compiled using
+# the include files in INCLUDES and setting the cache variable VAR
+# accordingly.
+ac_fn_c_check_header_mongrel ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ if eval \${$3+:} false; then :
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+ $as_echo_n "(cached) " >&6
+fi
+eval ac_res=\$$3
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+else
+ # Is the header compilable?
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5
+$as_echo_n "checking $2 usability... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+$4
+#include <$2>
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_header_compiler=yes
+else
+ ac_header_compiler=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5
+$as_echo "$ac_header_compiler" >&6; }
+
+# Is the header present?
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5
+$as_echo_n "checking $2 presence... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <$2>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+ ac_header_preproc=yes
+else
+ ac_header_preproc=no
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5
+$as_echo "$ac_header_preproc" >&6; }
+
+# So? What about this header?
+case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #((
+ yes:no: )
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5
+$as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
+$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
+ ;;
+ no:yes:* )
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5
+$as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: check for missing prerequisite headers?" >&5
+$as_echo "$as_me: WARNING: $2: check for missing prerequisite headers?" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5
+$as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&5
+$as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
+$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
+ ;;
+esac
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ eval "$3=\$ac_header_compiler"
+fi
+eval ac_res=\$$3
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+fi
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_header_mongrel
+
+# ac_fn_c_try_run LINENO
+# ----------------------
+# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes
+# that executables *can* be run.
+ac_fn_c_try_run ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ if { { ac_try="$ac_link"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_link") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } && { ac_try='./conftest$ac_exeext'
+ { { case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_try") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; }; then :
+ ac_retval=0
+else
+ $as_echo "$as_me: program exited with status $ac_status" >&5
+ $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ ac_retval=$ac_status
+fi
+ rm -rf conftest.dSYM conftest_ipa8_conftest.oo
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+ as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_run
+
+# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES
+# -------------------------------------------------------
+# Tests whether HEADER exists and can be compiled using the include files in
+# INCLUDES, setting the cache variable VAR accordingly.
+ac_fn_c_check_header_compile ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+$4
+#include <$2>
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ eval "$3=yes"
+else
+ eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_header_compile
+
+# ac_fn_c_try_link LINENO
+# -----------------------
+# Try to link conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_link ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ rm -f conftest.$ac_objext conftest$ac_exeext
+ if { { ac_try="$ac_link"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_link") 2>conftest.err
+ ac_status=$?
+ if test -s conftest.err; then
+ grep -v '^ *+' conftest.err >conftest.er1
+ cat conftest.er1 >&5
+ mv -f conftest.er1 conftest.err
+ fi
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } && {
+ test -z "$ac_c_werror_flag" ||
+ test ! -s conftest.err
+ } && test -s conftest$ac_exeext && {
+ test "$cross_compiling" = yes ||
+ test -x conftest$ac_exeext
+ }; then :
+ ac_retval=0
+else
+ $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ ac_retval=1
+fi
+ # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information
+ # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would
+ # interfere with the next link command; also delete a directory that is
+ # left behind by Apple's compiler. We do this before executing the actions.
+ rm -rf conftest.dSYM conftest_ipa8_conftest.oo
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+ as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_link
+
+# ac_fn_c_check_func LINENO FUNC VAR
+# ----------------------------------
+# Tests whether FUNC exists, setting the cache variable VAR accordingly
+ac_fn_c_check_func ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+/* Define $2 to an innocuous variant, in case <limits.h> declares $2.
+ For example, HP-UX 11i <limits.h> declares gettimeofday. */
+#define $2 innocuous_$2
+
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $2 (); below.
+ Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+ <limits.h> exists even on freestanding compilers. */
+
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+
+#undef $2
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char $2 ();
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined __stub_$2 || defined __stub___$2
+choke me
+#endif
+
+int
+main ()
+{
+return $2 ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ eval "$3=yes"
+else
+ eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_func
+
+# ac_fn_c_compute_int LINENO EXPR VAR INCLUDES
+# --------------------------------------------
+# Tries to find the compile-time value of EXPR in a program that includes
+# INCLUDES, setting VAR accordingly. Returns whether the value could be
+# computed
+ac_fn_c_compute_int ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ if test "$cross_compiling" = yes; then
+ # Depending upon the size, compute the lo and hi bounds.
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+$4
+int
+main ()
+{
+static int test_array [1 - 2 * !(($2) >= 0)];
+test_array [0] = 0;
+return test_array [0];
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_lo=0 ac_mid=0
+ while :; do
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+$4
+int
+main ()
+{
+static int test_array [1 - 2 * !(($2) <= $ac_mid)];
+test_array [0] = 0;
+return test_array [0];
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_hi=$ac_mid; break
+else
+ as_fn_arith $ac_mid + 1 && ac_lo=$as_val
+ if test $ac_lo -le $ac_mid; then
+ ac_lo= ac_hi=
+ break
+ fi
+ as_fn_arith 2 '*' $ac_mid + 1 && ac_mid=$as_val
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ done
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+$4
+int
+main ()
+{
+static int test_array [1 - 2 * !(($2) < 0)];
+test_array [0] = 0;
+return test_array [0];
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_hi=-1 ac_mid=-1
+ while :; do
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+$4
+int
+main ()
+{
+static int test_array [1 - 2 * !(($2) >= $ac_mid)];
+test_array [0] = 0;
+return test_array [0];
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_lo=$ac_mid; break
+else
+ as_fn_arith '(' $ac_mid ')' - 1 && ac_hi=$as_val
+ if test $ac_mid -le $ac_hi; then
+ ac_lo= ac_hi=
+ break
+ fi
+ as_fn_arith 2 '*' $ac_mid && ac_mid=$as_val
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ done
+else
+ ac_lo= ac_hi=
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+# Binary search between lo and hi bounds.
+while test "x$ac_lo" != "x$ac_hi"; do
+ as_fn_arith '(' $ac_hi - $ac_lo ')' / 2 + $ac_lo && ac_mid=$as_val
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+$4
+int
+main ()
+{
+static int test_array [1 - 2 * !(($2) <= $ac_mid)];
+test_array [0] = 0;
+return test_array [0];
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_hi=$ac_mid
+else
+ as_fn_arith '(' $ac_mid ')' + 1 && ac_lo=$as_val
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+done
+case $ac_lo in #((
+?*) eval "$3=\$ac_lo"; ac_retval=0 ;;
+'') ac_retval=1 ;;
+esac
+ else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+$4
+static long int longval () { return $2; }
+static unsigned long int ulongval () { return $2; }
+#include <stdio.h>
+#include <stdlib.h>
+int
+main ()
+{
+
+ FILE *f = fopen ("conftest.val", "w");
+ if (! f)
+ return 1;
+ if (($2) < 0)
+ {
+ long int i = longval ();
+ if (i != ($2))
+ return 1;
+ fprintf (f, "%ld", i);
+ }
+ else
+ {
+ unsigned long int i = ulongval ();
+ if (i != ($2))
+ return 1;
+ fprintf (f, "%lu", i);
+ }
+ /* Do not output a trailing newline, as this causes \r\n confusion
+ on some platforms. */
+ return ferror (f) || fclose (f) != 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+ echo >>conftest.val; read $3 <conftest.val; ac_retval=0
+else
+ ac_retval=1
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+ conftest.$ac_objext conftest.beam conftest.$ac_ext
+rm -f conftest.val
+
+ fi
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+ as_fn_set_status $ac_retval
+
+} # ac_fn_c_compute_int
+cat >config.log <<_ACEOF
+This file contains any messages produced by compilers while
+running configure, to aid debugging if configure makes a mistake.
+
+It was created by $as_me, which was
+generated by GNU Autoconf 2.69. Invocation command line was
+
+ $ $0 $@
+
+_ACEOF
+exec 5>>config.log
+{
+cat <<_ASUNAME
+## --------- ##
+## Platform. ##
+## --------- ##
+
+hostname = `(hostname || uname -n) 2>/dev/null | sed 1q`
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown`
+/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown`
+
+/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown`
+/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown`
+/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown`
+/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown`
+/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown`
+/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown`
+
+_ASUNAME
+
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ $as_echo "PATH: $as_dir"
+ done
+IFS=$as_save_IFS
+
+} >&5
+
+cat >&5 <<_ACEOF
+
+
+## ----------- ##
+## Core tests. ##
+## ----------- ##
+
+_ACEOF
+
+
+# Keep a trace of the command line.
+# Strip out --no-create and --no-recursion so they do not pile up.
+# Strip out --silent because we don't want to record it for future runs.
+# Also quote any args containing shell meta-characters.
+# Make two passes to allow for proper duplicate-argument suppression.
+ac_configure_args=
+ac_configure_args0=
+ac_configure_args1=
+ac_must_keep_next=false
+for ac_pass in 1 2
+do
+ for ac_arg
+ do
+ case $ac_arg in
+ -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;;
+ -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+ | -silent | --silent | --silen | --sile | --sil)
+ continue ;;
+ *\'*)
+ ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+ esac
+ case $ac_pass in
+ 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;;
+ 2)
+ as_fn_append ac_configure_args1 " '$ac_arg'"
+ if test $ac_must_keep_next = true; then
+ ac_must_keep_next=false # Got value, back to normal.
+ else
+ case $ac_arg in
+ *=* | --config-cache | -C | -disable-* | --disable-* \
+ | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \
+ | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \
+ | -with-* | --with-* | -without-* | --without-* | --x)
+ case "$ac_configure_args0 " in
+ "$ac_configure_args1"*" '$ac_arg' "* ) continue ;;
+ esac
+ ;;
+ -* ) ac_must_keep_next=true ;;
+ esac
+ fi
+ as_fn_append ac_configure_args " '$ac_arg'"
+ ;;
+ esac
+ done
+done
+{ ac_configure_args0=; unset ac_configure_args0;}
+{ ac_configure_args1=; unset ac_configure_args1;}
+
+# When interrupted or exit'd, cleanup temporary files, and complete
+# config.log. We remove comments because anyway the quotes in there
+# would cause problems or look ugly.
+# WARNING: Use '\'' to represent an apostrophe within the trap.
+# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug.
+trap 'exit_status=$?
+ # Save into config.log some information that might help in debugging.
+ {
+ echo
+
+ $as_echo "## ---------------- ##
+## Cache variables. ##
+## ---------------- ##"
+ echo
+ # The following way of writing the cache mishandles newlines in values,
+(
+ for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do
+ eval ac_val=\$$ac_var
+ case $ac_val in #(
+ *${as_nl}*)
+ case $ac_var in #(
+ *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+ esac
+ case $ac_var in #(
+ _ | IFS | as_nl) ;; #(
+ BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
+ *) { eval $ac_var=; unset $ac_var;} ;;
+ esac ;;
+ esac
+ done
+ (set) 2>&1 |
+ case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #(
+ *${as_nl}ac_space=\ *)
+ sed -n \
+ "s/'\''/'\''\\\\'\'''\''/g;
+ s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p"
+ ;; #(
+ *)
+ sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+ ;;
+ esac |
+ sort
+)
+ echo
+
+ $as_echo "## ----------------- ##
+## Output variables. ##
+## ----------------- ##"
+ echo
+ for ac_var in $ac_subst_vars
+ do
+ eval ac_val=\$$ac_var
+ case $ac_val in
+ *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+ esac
+ $as_echo "$ac_var='\''$ac_val'\''"
+ done | sort
+ echo
+
+ if test -n "$ac_subst_files"; then
+ $as_echo "## ------------------- ##
+## File substitutions. ##
+## ------------------- ##"
+ echo
+ for ac_var in $ac_subst_files
+ do
+ eval ac_val=\$$ac_var
+ case $ac_val in
+ *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+ esac
+ $as_echo "$ac_var='\''$ac_val'\''"
+ done | sort
+ echo
+ fi
+
+ if test -s confdefs.h; then
+ $as_echo "## ----------- ##
+## confdefs.h. ##
+## ----------- ##"
+ echo
+ cat confdefs.h
+ echo
+ fi
+ test "$ac_signal" != 0 &&
+ $as_echo "$as_me: caught signal $ac_signal"
+ $as_echo "$as_me: exit $exit_status"
+ } >&5
+ rm -f core *.core core.conftest.* &&
+ rm -f -r conftest* confdefs* conf$$* $ac_clean_files &&
+ exit $exit_status
+' 0
+for ac_signal in 1 2 13 15; do
+ trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal
+done
+ac_signal=0
+
+# confdefs.h avoids OS command line length limits that DEFS can exceed.
+rm -f -r conftest* confdefs.h
+
+$as_echo "/* confdefs.h */" > confdefs.h
+
+# Predefined preprocessor variables.
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_NAME "$PACKAGE_NAME"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_TARNAME "$PACKAGE_TARNAME"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_VERSION "$PACKAGE_VERSION"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_STRING "$PACKAGE_STRING"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_URL "$PACKAGE_URL"
+_ACEOF
+
+
+# Let the site file select an alternate cache file if it wants to.
+# Prefer an explicitly selected file to automatically selected ones.
+ac_site_file1=NONE
+ac_site_file2=NONE
+if test -n "$CONFIG_SITE"; then
+ # We do not want a PATH search for config.site.
+ case $CONFIG_SITE in #((
+ -*) ac_site_file1=./$CONFIG_SITE;;
+ */*) ac_site_file1=$CONFIG_SITE;;
+ *) ac_site_file1=./$CONFIG_SITE;;
+ esac
+elif test "x$prefix" != xNONE; then
+ ac_site_file1=$prefix/share/config.site
+ ac_site_file2=$prefix/etc/config.site
+else
+ ac_site_file1=$ac_default_prefix/share/config.site
+ ac_site_file2=$ac_default_prefix/etc/config.site
+fi
+for ac_site_file in "$ac_site_file1" "$ac_site_file2"
+do
+ test "x$ac_site_file" = xNONE && continue
+ if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5
+$as_echo "$as_me: loading site script $ac_site_file" >&6;}
+ sed 's/^/| /' "$ac_site_file" >&5
+ . "$ac_site_file" \
+ || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "failed to load site script $ac_site_file
+See \`config.log' for more details" "$LINENO" 5; }
+ fi
+done
+
+if test -r "$cache_file"; then
+ # Some versions of bash will fail to source /dev/null (special files
+ # actually), so we avoid doing that. DJGPP emulates it as a regular file.
+ if test /dev/null != "$cache_file" && test -f "$cache_file"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5
+$as_echo "$as_me: loading cache $cache_file" >&6;}
+ case $cache_file in
+ [\\/]* | ?:[\\/]* ) . "$cache_file";;
+ *) . "./$cache_file";;
+ esac
+ fi
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5
+$as_echo "$as_me: creating cache $cache_file" >&6;}
+ >$cache_file
+fi
+
+# Check that the precious variables saved in the cache have kept the same
+# value.
+ac_cache_corrupted=false
+for ac_var in $ac_precious_vars; do
+ eval ac_old_set=\$ac_cv_env_${ac_var}_set
+ eval ac_new_set=\$ac_env_${ac_var}_set
+ eval ac_old_val=\$ac_cv_env_${ac_var}_value
+ eval ac_new_val=\$ac_env_${ac_var}_value
+ case $ac_old_set,$ac_new_set in
+ set,)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5
+$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;}
+ ac_cache_corrupted=: ;;
+ ,set)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5
+$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;}
+ ac_cache_corrupted=: ;;
+ ,);;
+ *)
+ if test "x$ac_old_val" != "x$ac_new_val"; then
+ # differences in whitespace do not lead to failure.
+ ac_old_val_w=`echo x $ac_old_val`
+ ac_new_val_w=`echo x $ac_new_val`
+ if test "$ac_old_val_w" != "$ac_new_val_w"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5
+$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;}
+ ac_cache_corrupted=:
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5
+$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;}
+ eval $ac_var=\$ac_old_val
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5
+$as_echo "$as_me: former value: \`$ac_old_val'" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5
+$as_echo "$as_me: current value: \`$ac_new_val'" >&2;}
+ fi;;
+ esac
+ # Pass precious variables to config.status.
+ if test "$ac_new_set" = set; then
+ case $ac_new_val in
+ *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;;
+ *) ac_arg=$ac_var=$ac_new_val ;;
+ esac
+ case " $ac_configure_args " in
+ *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy.
+ *) as_fn_append ac_configure_args " '$ac_arg'" ;;
+ esac
+ fi
+done
+if $ac_cache_corrupted; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5
+$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;}
+ as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5
+fi
+## -------------------- ##
+## Main body of script. ##
+## -------------------- ##
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+
+VERSION="`cat VERSION`"
+PACKAGE=aprx
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5
+$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; }
+set x ${MAKE-make}
+ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'`
+if eval \${ac_cv_prog_make_${ac_make}_set+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat >conftest.make <<\_ACEOF
+SHELL = /bin/sh
+all:
+ @echo '@@@%%%=$(MAKE)=@@@%%%'
+_ACEOF
+# GNU make sometimes prints "make[1]: Entering ...", which would confuse us.
+case `${MAKE-make} -f conftest.make 2>/dev/null` in
+ *@@@%%%=?*=@@@%%%*)
+ eval ac_cv_prog_make_${ac_make}_set=yes;;
+ *)
+ eval ac_cv_prog_make_${ac_make}_set=no;;
+esac
+rm -f conftest.make
+fi
+if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ SET_MAKE=
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ SET_MAKE="MAKE=${MAKE-make}"
+fi
+
+
+ac_config_headers="$ac_config_headers config.h"
+
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}gcc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_CC="${ac_tool_prefix}gcc"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_CC"; then
+ ac_ct_CC=$CC
+ # Extract the first word of "gcc", so it can be a program name with args.
+set dummy gcc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_CC+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$ac_ct_CC"; then
+ ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_CC="gcc"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+$as_echo "$ac_ct_CC" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+ if test "x$ac_ct_CC" = x; then
+ CC=""
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ CC=$ac_ct_CC
+ fi
+else
+ CC="$ac_cv_prog_CC"
+fi
+
+if test -z "$CC"; then
+ if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}cc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_CC="${ac_tool_prefix}cc"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ fi
+fi
+if test -z "$CC"; then
+ # Extract the first word of "cc", so it can be a program name with args.
+set dummy cc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+ ac_prog_rejected=no
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then
+ ac_prog_rejected=yes
+ continue
+ fi
+ ac_cv_prog_CC="cc"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+if test $ac_prog_rejected = yes; then
+ # We found a bogon in the path, so make sure we never use it.
+ set dummy $ac_cv_prog_CC
+ shift
+ if test $# != 0; then
+ # We chose a different compiler from the bogus one.
+ # However, it has the same basename, so the bogon will be chosen
+ # first if we set CC to just the basename; use the full file name.
+ shift
+ ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@"
+ fi
+fi
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$CC"; then
+ if test -n "$ac_tool_prefix"; then
+ for ac_prog in cl.exe
+ do
+ # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_CC="$ac_tool_prefix$ac_prog"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$CC" && break
+ done
+fi
+if test -z "$CC"; then
+ ac_ct_CC=$CC
+ for ac_prog in cl.exe
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_CC+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$ac_ct_CC"; then
+ ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_CC="$ac_prog"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+$as_echo "$ac_ct_CC" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$ac_ct_CC" && break
+done
+
+ if test "x$ac_ct_CC" = x; then
+ CC=""
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ CC=$ac_ct_CC
+ fi
+fi
+
+fi
+
+
+test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "no acceptable C compiler found in \$PATH
+See \`config.log' for more details" "$LINENO" 5; }
+
+# Provide some information about the compiler.
+$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5
+set X $ac_compile
+ac_compiler=$2
+for ac_option in --version -v -V -qversion; do
+ { { ac_try="$ac_compiler $ac_option >&5"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_compiler $ac_option >&5") 2>conftest.err
+ ac_status=$?
+ if test -s conftest.err; then
+ sed '10a\
+... rest of stderr output deleted ...
+ 10q' conftest.err >conftest.er1
+ cat conftest.er1 >&5
+ fi
+ rm -f conftest.er1 conftest.err
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }
+done
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out"
+# Try to create an executable without -o first, disregard a.out.
+# It will help us diagnose broken compilers, and finding out an intuition
+# of exeext.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5
+$as_echo_n "checking whether the C compiler works... " >&6; }
+ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'`
+
+# The possible output files:
+ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*"
+
+ac_rmfiles=
+for ac_file in $ac_files
+do
+ case $ac_file in
+ *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
+ * ) ac_rmfiles="$ac_rmfiles $ac_file";;
+ esac
+done
+rm -f $ac_rmfiles
+
+if { { ac_try="$ac_link_default"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_link_default") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then :
+ # Autoconf-2.13 could set the ac_cv_exeext variable to `no'.
+# So ignore a value of `no', otherwise this would lead to `EXEEXT = no'
+# in a Makefile. We should not override ac_cv_exeext if it was cached,
+# so that the user can short-circuit this test for compilers unknown to
+# Autoconf.
+for ac_file in $ac_files ''
+do
+ test -f "$ac_file" || continue
+ case $ac_file in
+ *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj )
+ ;;
+ [ab].out )
+ # We found the default executable, but exeext='' is most
+ # certainly right.
+ break;;
+ *.* )
+ if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no;
+ then :; else
+ ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+ fi
+ # We set ac_cv_exeext here because the later test for it is not
+ # safe: cross compilers may not add the suffix if given an `-o'
+ # argument, so we may need to know it at that point already.
+ # Even if this section looks crufty: it has the advantage of
+ # actually working.
+ break;;
+ * )
+ break;;
+ esac
+done
+test "$ac_cv_exeext" = no && ac_cv_exeext=
+
+else
+ ac_file=''
+fi
+if test -z "$ac_file"; then :
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+$as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "C compiler cannot create executables
+See \`config.log' for more details" "$LINENO" 5; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5
+$as_echo_n "checking for C compiler default output file name... " >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5
+$as_echo "$ac_file" >&6; }
+ac_exeext=$ac_cv_exeext
+
+rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out
+ac_clean_files=$ac_clean_files_save
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5
+$as_echo_n "checking for suffix of executables... " >&6; }
+if { { ac_try="$ac_link"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_link") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then :
+ # If both `conftest.exe' and `conftest' are `present' (well, observable)
+# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will
+# work properly (i.e., refer to `conftest.exe'), while it won't with
+# `rm'.
+for ac_file in conftest.exe conftest conftest.*; do
+ test -f "$ac_file" || continue
+ case $ac_file in
+ *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
+ *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+ break;;
+ * ) break;;
+ esac
+done
+else
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot compute suffix of executables: cannot compile and link
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+rm -f conftest conftest$ac_cv_exeext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5
+$as_echo "$ac_cv_exeext" >&6; }
+
+rm -f conftest.$ac_ext
+EXEEXT=$ac_cv_exeext
+ac_exeext=$EXEEXT
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <stdio.h>
+int
+main ()
+{
+FILE *f = fopen ("conftest.out", "w");
+ return ferror (f) || fclose (f) != 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+ac_clean_files="$ac_clean_files conftest.out"
+# Check that the compiler produces executables we can run. If not, either
+# the compiler is broken, or we cross compile.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5
+$as_echo_n "checking whether we are cross compiling... " >&6; }
+if test "$cross_compiling" != yes; then
+ { { ac_try="$ac_link"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_link") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }
+ if { ac_try='./conftest$ac_cv_exeext'
+ { { case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_try") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; }; then
+ cross_compiling=no
+ else
+ if test "$cross_compiling" = maybe; then
+ cross_compiling=yes
+ else
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot run C compiled programs.
+If you meant to cross compile, use \`--host'.
+See \`config.log' for more details" "$LINENO" 5; }
+ fi
+ fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5
+$as_echo "$cross_compiling" >&6; }
+
+rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out
+ac_clean_files=$ac_clean_files_save
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5
+$as_echo_n "checking for suffix of object files... " >&6; }
+if ${ac_cv_objext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.o conftest.obj
+if { { ac_try="$ac_compile"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_compile") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then :
+ for ac_file in conftest.o conftest.obj conftest.*; do
+ test -f "$ac_file" || continue;
+ case $ac_file in
+ *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;;
+ *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'`
+ break;;
+ esac
+done
+else
+ $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot compute suffix of object files: cannot compile
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+rm -f conftest.$ac_cv_objext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5
+$as_echo "$ac_cv_objext" >&6; }
+OBJEXT=$ac_cv_objext
+ac_objext=$OBJEXT
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5
+$as_echo_n "checking whether we are using the GNU C compiler... " >&6; }
+if ${ac_cv_c_compiler_gnu+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+#ifndef __GNUC__
+ choke me
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_compiler_gnu=yes
+else
+ ac_compiler_gnu=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_cv_c_compiler_gnu=$ac_compiler_gnu
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5
+$as_echo "$ac_cv_c_compiler_gnu" >&6; }
+if test $ac_compiler_gnu = yes; then
+ GCC=yes
+else
+ GCC=
+fi
+ac_test_CFLAGS=${CFLAGS+set}
+ac_save_CFLAGS=$CFLAGS
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5
+$as_echo_n "checking whether $CC accepts -g... " >&6; }
+if ${ac_cv_prog_cc_g+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_save_c_werror_flag=$ac_c_werror_flag
+ ac_c_werror_flag=yes
+ ac_cv_prog_cc_g=no
+ CFLAGS="-g"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_cv_prog_cc_g=yes
+else
+ CFLAGS=""
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+ ac_c_werror_flag=$ac_save_c_werror_flag
+ CFLAGS="-g"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_cv_prog_cc_g=yes
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_c_werror_flag=$ac_save_c_werror_flag
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5
+$as_echo "$ac_cv_prog_cc_g" >&6; }
+if test "$ac_test_CFLAGS" = set; then
+ CFLAGS=$ac_save_CFLAGS
+elif test $ac_cv_prog_cc_g = yes; then
+ if test "$GCC" = yes; then
+ CFLAGS="-g -O2"
+ else
+ CFLAGS="-g"
+ fi
+else
+ if test "$GCC" = yes; then
+ CFLAGS="-O2"
+ else
+ CFLAGS=
+ fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5
+$as_echo_n "checking for $CC option to accept ISO C89... " >&6; }
+if ${ac_cv_prog_cc_c89+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_cv_prog_cc_c89=no
+ac_save_CC=$CC
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <stdarg.h>
+#include <stdio.h>
+struct stat;
+/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */
+struct buf { int x; };
+FILE * (*rcsopen) (struct buf *, struct stat *, int);
+static char *e (p, i)
+ char **p;
+ int i;
+{
+ return p[i];
+}
+static char *f (char * (*g) (char **, int), char **p, ...)
+{
+ char *s;
+ va_list v;
+ va_start (v,p);
+ s = g (p, va_arg (v,int));
+ va_end (v);
+ return s;
+}
+
+/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has
+ function prototypes and stuff, but not '\xHH' hex character constants.
+ These don't provoke an error unfortunately, instead are silently treated
+ as 'x'. The following induces an error, until -std is added to get
+ proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an
+ array size at least. It's necessary to write '\x00'==0 to get something
+ that's true only with -std. */
+int osf4_cc_array ['\x00' == 0 ? 1 : -1];
+
+/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters
+ inside strings and character constants. */
+#define FOO(x) 'x'
+int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1];
+
+int test (int i, double x);
+struct s1 {int (*f) (int a);};
+struct s2 {int (*f) (double a);};
+int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int);
+int argc;
+char **argv;
+int
+main ()
+{
+return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1];
+ ;
+ return 0;
+}
+_ACEOF
+for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \
+ -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__"
+do
+ CC="$ac_save_CC $ac_arg"
+ if ac_fn_c_try_compile "$LINENO"; then :
+ ac_cv_prog_cc_c89=$ac_arg
+fi
+rm -f core conftest.err conftest.$ac_objext
+ test "x$ac_cv_prog_cc_c89" != "xno" && break
+done
+rm -f conftest.$ac_ext
+CC=$ac_save_CC
+
+fi
+# AC_CACHE_VAL
+case "x$ac_cv_prog_cc_c89" in
+ x)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
+$as_echo "none needed" >&6; } ;;
+ xno)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
+$as_echo "unsupported" >&6; } ;;
+ *)
+ CC="$CC $ac_cv_prog_cc_c89"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5
+$as_echo "$ac_cv_prog_cc_c89" >&6; } ;;
+esac
+if test "x$ac_cv_prog_cc_c89" != xno; then :
+
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5
+$as_echo_n "checking how to run the C preprocessor... " >&6; }
+# On Suns, sometimes $CPP names a directory.
+if test -n "$CPP" && test -d "$CPP"; then
+ CPP=
+fi
+if test -z "$CPP"; then
+ if ${ac_cv_prog_CPP+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ # Double quotes because CPP needs to be expanded
+ for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp"
+ do
+ ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+ # Use a header file that comes with gcc, so configuring glibc
+ # with a fresh cross-compiler works.
+ # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+ # <limits.h> exists even on freestanding compilers.
+ # On the NeXT, cc -E runs the code through the compiler's parser,
+ # not just through cpp. "Syntax error" is here to catch this case.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+ Syntax error
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+
+else
+ # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+ # OK, works on sane cases. Now check whether nonexistent headers
+ # can be detected and how.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+ # Broken: success on invalid input.
+continue
+else
+ # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+ break
+fi
+
+ done
+ ac_cv_prog_CPP=$CPP
+
+fi
+ CPP=$ac_cv_prog_CPP
+else
+ ac_cv_prog_CPP=$CPP
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5
+$as_echo "$CPP" >&6; }
+ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+ # Use a header file that comes with gcc, so configuring glibc
+ # with a fresh cross-compiler works.
+ # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+ # <limits.h> exists even on freestanding compilers.
+ # On the NeXT, cc -E runs the code through the compiler's parser,
+ # not just through cpp. "Syntax error" is here to catch this case.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+ Syntax error
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+
+else
+ # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+ # OK, works on sane cases. Now check whether nonexistent headers
+ # can be detected and how.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+ # Broken: success on invalid input.
+continue
+else
+ # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+
+else
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "C preprocessor \"$CPP\" fails sanity check
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5
+$as_echo_n "checking for grep that handles long lines and -e... " >&6; }
+if ${ac_cv_path_GREP+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -z "$GREP"; then
+ ac_path_GREP_found=false
+ # Loop through the user's path and test for each of PROGNAME-LIST
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_prog in grep ggrep; do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext"
+ as_fn_executable_p "$ac_path_GREP" || continue
+# Check for GNU ac_path_GREP and select it if it is found.
+ # Check for GNU $ac_path_GREP
+case `"$ac_path_GREP" --version 2>&1` in
+*GNU*)
+ ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;;
+*)
+ ac_count=0
+ $as_echo_n 0123456789 >"conftest.in"
+ while :
+ do
+ cat "conftest.in" "conftest.in" >"conftest.tmp"
+ mv "conftest.tmp" "conftest.in"
+ cp "conftest.in" "conftest.nl"
+ $as_echo 'GREP' >> "conftest.nl"
+ "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+ diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+ as_fn_arith $ac_count + 1 && ac_count=$as_val
+ if test $ac_count -gt ${ac_path_GREP_max-0}; then
+ # Best one so far, save it but keep looking for a better one
+ ac_cv_path_GREP="$ac_path_GREP"
+ ac_path_GREP_max=$ac_count
+ fi
+ # 10*(2^10) chars as input seems more than enough
+ test $ac_count -gt 10 && break
+ done
+ rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+ $ac_path_GREP_found && break 3
+ done
+ done
+ done
+IFS=$as_save_IFS
+ if test -z "$ac_cv_path_GREP"; then
+ as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+ fi
+else
+ ac_cv_path_GREP=$GREP
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5
+$as_echo "$ac_cv_path_GREP" >&6; }
+ GREP="$ac_cv_path_GREP"
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5
+$as_echo_n "checking for egrep... " >&6; }
+if ${ac_cv_path_EGREP+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if echo a | $GREP -E '(a|b)' >/dev/null 2>&1
+ then ac_cv_path_EGREP="$GREP -E"
+ else
+ if test -z "$EGREP"; then
+ ac_path_EGREP_found=false
+ # Loop through the user's path and test for each of PROGNAME-LIST
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_prog in egrep; do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext"
+ as_fn_executable_p "$ac_path_EGREP" || continue
+# Check for GNU ac_path_EGREP and select it if it is found.
+ # Check for GNU $ac_path_EGREP
+case `"$ac_path_EGREP" --version 2>&1` in
+*GNU*)
+ ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;;
+*)
+ ac_count=0
+ $as_echo_n 0123456789 >"conftest.in"
+ while :
+ do
+ cat "conftest.in" "conftest.in" >"conftest.tmp"
+ mv "conftest.tmp" "conftest.in"
+ cp "conftest.in" "conftest.nl"
+ $as_echo 'EGREP' >> "conftest.nl"
+ "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+ diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+ as_fn_arith $ac_count + 1 && ac_count=$as_val
+ if test $ac_count -gt ${ac_path_EGREP_max-0}; then
+ # Best one so far, save it but keep looking for a better one
+ ac_cv_path_EGREP="$ac_path_EGREP"
+ ac_path_EGREP_max=$ac_count
+ fi
+ # 10*(2^10) chars as input seems more than enough
+ test $ac_count -gt 10 && break
+ done
+ rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+ $ac_path_EGREP_found && break 3
+ done
+ done
+ done
+IFS=$as_save_IFS
+ if test -z "$ac_cv_path_EGREP"; then
+ as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+ fi
+else
+ ac_cv_path_EGREP=$EGREP
+fi
+
+ fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5
+$as_echo "$ac_cv_path_EGREP" >&6; }
+ EGREP="$ac_cv_path_EGREP"
+
+
+if test $ac_cv_c_compiler_gnu = yes; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC needs -traditional" >&5
+$as_echo_n "checking whether $CC needs -traditional... " >&6; }
+if ${ac_cv_prog_gcc_traditional+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_pattern="Autoconf.*'x'"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <sgtty.h>
+Autoconf TIOCGETP
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "$ac_pattern" >/dev/null 2>&1; then :
+ ac_cv_prog_gcc_traditional=yes
+else
+ ac_cv_prog_gcc_traditional=no
+fi
+rm -f conftest*
+
+
+ if test $ac_cv_prog_gcc_traditional = no; then
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <termio.h>
+Autoconf TCGETA
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "$ac_pattern" >/dev/null 2>&1; then :
+ ac_cv_prog_gcc_traditional=yes
+fi
+rm -f conftest*
+
+ fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_gcc_traditional" >&5
+$as_echo "$ac_cv_prog_gcc_traditional" >&6; }
+ if test $ac_cv_prog_gcc_traditional = yes; then
+ CC="$CC -traditional"
+ fi
+fi
+
+
+if test -z "$LD" ; then
+ LD="$CC"
+fi
+LD="$LD"
+
+
+MACHINE="`uname -m`"
+if test "$MACHINE" == "i686" -o "$MACHINE" == "i386"; then
+ CFLAGS_ARCH="-march=i686"
+fi
+OS="`uname`"
+if test "$OS" == "Darwin"; then
+ CFLAGS_ARCH=""
+fi
+
+
+AX_CHECK_GNU_MAKE()
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5
+$as_echo_n "checking for ANSI C header files... " >&6; }
+if ${ac_cv_header_stdc+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <float.h>
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_cv_header_stdc=yes
+else
+ ac_cv_header_stdc=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+if test $ac_cv_header_stdc = yes; then
+ # SunOS 4.x string.h does not declare mem*, contrary to ANSI.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <string.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "memchr" >/dev/null 2>&1; then :
+
+else
+ ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+ # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <stdlib.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "free" >/dev/null 2>&1; then :
+
+else
+ ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+ # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi.
+ if test "$cross_compiling" = yes; then :
+ :
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <ctype.h>
+#include <stdlib.h>
+#if ((' ' & 0x0FF) == 0x020)
+# define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
+# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c))
+#else
+# define ISLOWER(c) \
+ (('a' <= (c) && (c) <= 'i') \
+ || ('j' <= (c) && (c) <= 'r') \
+ || ('s' <= (c) && (c) <= 'z'))
+# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c))
+#endif
+
+#define XOR(e, f) (((e) && !(f)) || (!(e) && (f)))
+int
+main ()
+{
+ int i;
+ for (i = 0; i < 256; i++)
+ if (XOR (islower (i), ISLOWER (i))
+ || toupper (i) != TOUPPER (i))
+ return 2;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+
+else
+ ac_cv_header_stdc=no
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+ conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5
+$as_echo "$ac_cv_header_stdc" >&6; }
+if test $ac_cv_header_stdc = yes; then
+
+$as_echo "#define STDC_HEADERS 1" >>confdefs.h
+
+fi
+
+# On IRIX 5.3, sys/types and inttypes.h are conflicting.
+for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \
+ inttypes.h stdint.h unistd.h
+do :
+ as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
+ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default
+"
+if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
+ cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+
+done
+
+
+for ac_header in time.h sys/time.h stdlib.h stddef.h stdint.h
+do :
+ as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
+ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"
+if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
+ cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+
+done
+
+for ac_header in string.h strings.h
+do :
+ as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
+ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"
+if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
+ cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+
+done
+
+for ac_header in pty.h
+do :
+ ac_fn_c_check_header_mongrel "$LINENO" "pty.h" "ac_cv_header_pty_h" "$ac_includes_default"
+if test "x$ac_cv_header_pty_h" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_PTY_H 1
+_ACEOF
+
+fi
+
+done
+
+for ac_header in pthread.h
+do :
+ ac_fn_c_check_header_mongrel "$LINENO" "pthread.h" "ac_cv_header_pthread_h" "$ac_includes_default"
+if test "x$ac_cv_header_pthread_h" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_PTHREAD_H 1
+_ACEOF
+
+fi
+
+done
+
+
+for ac_header in alloca.h
+do :
+ ac_fn_c_check_header_mongrel "$LINENO" "alloca.h" "ac_cv_header_alloca_h" "$ac_includes_default"
+if test "x$ac_cv_header_alloca_h" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_ALLOCA_H 1
+_ACEOF
+ $as_echo "#define HAVE_ALLOCA_H 1" >>confdefs.h
+
+fi
+
+done
+
+for ac_header in poll.h
+do :
+ ac_fn_c_check_header_mongrel "$LINENO" "poll.h" "ac_cv_header_poll_h" "$ac_includes_default"
+if test "x$ac_cv_header_poll_h" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_POLL_H 1
+_ACEOF
+ $as_echo "#define HAVE_POLL_H 1" >>confdefs.h
+
+fi
+
+done
+
+for ac_header in sys/epoll.h
+do :
+ ac_fn_c_check_header_mongrel "$LINENO" "sys/epoll.h" "ac_cv_header_sys_epoll_h" "$ac_includes_default"
+if test "x$ac_cv_header_sys_epoll_h" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_SYS_EPOLL_H 1
+_ACEOF
+ $as_echo "#define HAVE_SYS_EPOLL_H 1" >>confdefs.h
+
+fi
+
+done
+
+
+for ac_header in netinet/sctp.h
+do :
+ ac_fn_c_check_header_mongrel "$LINENO" "netinet/sctp.h" "ac_cv_header_netinet_sctp_h" "$ac_includes_default"
+if test "x$ac_cv_header_netinet_sctp_h" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_NETINET_SCTP_H 1
+_ACEOF
+ $as_echo "#define HAVE_NETINET_SCTP_H 1" >>confdefs.h
+
+fi
+
+done
+
+
+
+
+for ac_header in stdarg.h varargs.h sys/wait.h
+do :
+ as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
+ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"
+if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
+ cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+
+done
+
+for ac_func in vprintf
+do :
+ ac_fn_c_check_func "$LINENO" "vprintf" "ac_cv_func_vprintf"
+if test "x$ac_cv_func_vprintf" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_VPRINTF 1
+_ACEOF
+
+ac_fn_c_check_func "$LINENO" "_doprnt" "ac_cv_func__doprnt"
+if test "x$ac_cv_func__doprnt" = xyes; then :
+
+$as_echo "#define HAVE_DOPRNT 1" >>confdefs.h
+
+fi
+
+fi
+done
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: ** Using C compiler: $CC" >&5
+$as_echo "** Using C compiler: $CC" >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: ** Using CFLAGS: $CFLAGS" >&5
+$as_echo "** Using CFLAGS: $CFLAGS" >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: ** Using CPPDEP: $CPPDEP" >&5
+$as_echo "** Using CPPDEP: $CPPDEP" >&6; }
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether byte ordering is bigendian" >&5
+$as_echo_n "checking whether byte ordering is bigendian... " >&6; }
+if ${ac_cv_c_bigendian+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_cv_c_bigendian=unknown
+ # See if we're dealing with a universal compiler.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#ifndef __APPLE_CC__
+ not a universal capable compiler
+ #endif
+ typedef int dummy;
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ # Check for potential -arch flags. It is not universal unless
+ # there are at least two -arch flags with different values.
+ ac_arch=
+ ac_prev=
+ for ac_word in $CC $CFLAGS $CPPFLAGS $LDFLAGS; do
+ if test -n "$ac_prev"; then
+ case $ac_word in
+ i?86 | x86_64 | ppc | ppc64)
+ if test -z "$ac_arch" || test "$ac_arch" = "$ac_word"; then
+ ac_arch=$ac_word
+ else
+ ac_cv_c_bigendian=universal
+ break
+ fi
+ ;;
+ esac
+ ac_prev=
+ elif test "x$ac_word" = "x-arch"; then
+ ac_prev=arch
+ fi
+ done
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ if test $ac_cv_c_bigendian = unknown; then
+ # See if sys/param.h defines the BYTE_ORDER macro.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <sys/types.h>
+ #include <sys/param.h>
+
+int
+main ()
+{
+#if ! (defined BYTE_ORDER && defined BIG_ENDIAN \
+ && defined LITTLE_ENDIAN && BYTE_ORDER && BIG_ENDIAN \
+ && LITTLE_ENDIAN)
+ bogus endian macros
+ #endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ # It does; now see whether it defined to BIG_ENDIAN or not.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <sys/types.h>
+ #include <sys/param.h>
+
+int
+main ()
+{
+#if BYTE_ORDER != BIG_ENDIAN
+ not big endian
+ #endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_cv_c_bigendian=yes
+else
+ ac_cv_c_bigendian=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ fi
+ if test $ac_cv_c_bigendian = unknown; then
+ # See if <limits.h> defines _LITTLE_ENDIAN or _BIG_ENDIAN (e.g., Solaris).
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <limits.h>
+
+int
+main ()
+{
+#if ! (defined _LITTLE_ENDIAN || defined _BIG_ENDIAN)
+ bogus endian macros
+ #endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ # It does; now see whether it defined to _BIG_ENDIAN or not.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <limits.h>
+
+int
+main ()
+{
+#ifndef _BIG_ENDIAN
+ not big endian
+ #endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_cv_c_bigendian=yes
+else
+ ac_cv_c_bigendian=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ fi
+ if test $ac_cv_c_bigendian = unknown; then
+ # Compile a test program.
+ if test "$cross_compiling" = yes; then :
+ # Try to guess by grepping values from an object file.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+short int ascii_mm[] =
+ { 0x4249, 0x4765, 0x6E44, 0x6961, 0x6E53, 0x7953, 0 };
+ short int ascii_ii[] =
+ { 0x694C, 0x5454, 0x656C, 0x6E45, 0x6944, 0x6E61, 0 };
+ int use_ascii (int i) {
+ return ascii_mm[i] + ascii_ii[i];
+ }
+ short int ebcdic_ii[] =
+ { 0x89D3, 0xE3E3, 0x8593, 0x95C5, 0x89C4, 0x9581, 0 };
+ short int ebcdic_mm[] =
+ { 0xC2C9, 0xC785, 0x95C4, 0x8981, 0x95E2, 0xA8E2, 0 };
+ int use_ebcdic (int i) {
+ return ebcdic_mm[i] + ebcdic_ii[i];
+ }
+ extern int foo;
+
+int
+main ()
+{
+return use_ascii (foo) == use_ebcdic (foo);
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ if grep BIGenDianSyS conftest.$ac_objext >/dev/null; then
+ ac_cv_c_bigendian=yes
+ fi
+ if grep LiTTleEnDian conftest.$ac_objext >/dev/null ; then
+ if test "$ac_cv_c_bigendian" = unknown; then
+ ac_cv_c_bigendian=no
+ else
+ # finding both strings is unlikely to happen, but who knows?
+ ac_cv_c_bigendian=unknown
+ fi
+ fi
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+$ac_includes_default
+int
+main ()
+{
+
+ /* Are we little or big endian? From Harbison&Steele. */
+ union
+ {
+ long int l;
+ char c[sizeof (long int)];
+ } u;
+ u.l = 1;
+ return u.c[sizeof (long int) - 1] == 1;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+ ac_cv_c_bigendian=no
+else
+ ac_cv_c_bigendian=yes
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+ conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+ fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_bigendian" >&5
+$as_echo "$ac_cv_c_bigendian" >&6; }
+ case $ac_cv_c_bigendian in #(
+ yes)
+ $as_echo "#define WORDS_BIGENDIAN 1" >>confdefs.h
+;; #(
+ no)
+ ;; #(
+ universal)
+
+$as_echo "#define AC_APPLE_UNIVERSAL_BUILD 1" >>confdefs.h
+
+ ;; #(
+ *)
+ as_fn_error $? "unknown endianness
+ presetting ac_cv_c_bigendian=no (or yes) will help" "$LINENO" 5 ;;
+ esac
+
+# The cast to long int works around a bug in the HP C Compiler
+# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects
+# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'.
+# This bug is HP SR number 8606223364.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking size of void *" >&5
+$as_echo_n "checking size of void *... " >&6; }
+if ${ac_cv_sizeof_void_p+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (void *))" "ac_cv_sizeof_void_p" "$ac_includes_default"; then :
+
+else
+ if test "$ac_cv_type_void_p" = yes; then
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "cannot compute sizeof (void *)
+See \`config.log' for more details" "$LINENO" 5; }
+ else
+ ac_cv_sizeof_void_p=0
+ fi
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_void_p" >&5
+$as_echo "$ac_cv_sizeof_void_p" >&6; }
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define SIZEOF_VOID_P $ac_cv_sizeof_void_p
+_ACEOF
+
+
+# The cast to long int works around a bug in the HP C Compiler
+# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects
+# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'.
+# This bug is HP SR number 8606223364.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking size of short" >&5
+$as_echo_n "checking size of short... " >&6; }
+if ${ac_cv_sizeof_short+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (short))" "ac_cv_sizeof_short" "$ac_includes_default"; then :
+
+else
+ if test "$ac_cv_type_short" = yes; then
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "cannot compute sizeof (short)
+See \`config.log' for more details" "$LINENO" 5; }
+ else
+ ac_cv_sizeof_short=0
+ fi
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_short" >&5
+$as_echo "$ac_cv_sizeof_short" >&6; }
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define SIZEOF_SHORT $ac_cv_sizeof_short
+_ACEOF
+
+
+# The cast to long int works around a bug in the HP C Compiler
+# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects
+# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'.
+# This bug is HP SR number 8606223364.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking size of int" >&5
+$as_echo_n "checking size of int... " >&6; }
+if ${ac_cv_sizeof_int+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (int))" "ac_cv_sizeof_int" "$ac_includes_default"; then :
+
+else
+ if test "$ac_cv_type_int" = yes; then
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "cannot compute sizeof (int)
+See \`config.log' for more details" "$LINENO" 5; }
+ else
+ ac_cv_sizeof_int=0
+ fi
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_int" >&5
+$as_echo "$ac_cv_sizeof_int" >&6; }
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define SIZEOF_INT $ac_cv_sizeof_int
+_ACEOF
+
+
+# The cast to long int works around a bug in the HP C Compiler
+# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects
+# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'.
+# This bug is HP SR number 8606223364.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking size of long" >&5
+$as_echo_n "checking size of long... " >&6; }
+if ${ac_cv_sizeof_long+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (long))" "ac_cv_sizeof_long" "$ac_includes_default"; then :
+
+else
+ if test "$ac_cv_type_long" = yes; then
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "cannot compute sizeof (long)
+See \`config.log' for more details" "$LINENO" 5; }
+ else
+ ac_cv_sizeof_long=0
+ fi
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_long" >&5
+$as_echo "$ac_cv_sizeof_long" >&6; }
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define SIZEOF_LONG $ac_cv_sizeof_long
+_ACEOF
+
+
+# The cast to long int works around a bug in the HP C Compiler
+# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects
+# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'.
+# This bug is HP SR number 8606223364.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking size of double" >&5
+$as_echo_n "checking size of double... " >&6; }
+if ${ac_cv_sizeof_double+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (double))" "ac_cv_sizeof_double" "$ac_includes_default"; then :
+
+else
+ if test "$ac_cv_type_double" = yes; then
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "cannot compute sizeof (double)
+See \`config.log' for more details" "$LINENO" 5; }
+ else
+ ac_cv_sizeof_double=0
+ fi
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_double" >&5
+$as_echo "$ac_cv_sizeof_double" >&6; }
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define SIZEOF_DOUBLE $ac_cv_sizeof_double
+_ACEOF
+
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define CONFIGURE_CMD "CC='$CC' CFLAGS='$CFLAGS' $0 $ac_configure_args"
+_ACEOF
+
+
+
+
+for ac_func in atan2f
+do :
+ ac_fn_c_check_func "$LINENO" "atan2f" "ac_cv_func_atan2f"
+if test "x$ac_cv_func_atan2f" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_ATAN2F 1
+_ACEOF
+
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for atan2f in -lm" >&5
+$as_echo_n "checking for atan2f in -lm... " >&6; }
+if ${ac_cv_lib_m_atan2f+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lm $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char atan2f ();
+int
+main ()
+{
+return atan2f ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_m_atan2f=yes
+else
+ ac_cv_lib_m_atan2f=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_m_atan2f" >&5
+$as_echo "$ac_cv_lib_m_atan2f" >&6; }
+if test "x$ac_cv_lib_m_atan2f" = xyes; then :
+ LIBM="-lm"
+fi
+
+fi
+done
+
+
+for ac_func in memchr memrchr gettimeofday
+do :
+ as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
+ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
+if eval test \"x\$"$as_ac_var"\" = x"yes"; then :
+ cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+done
+
+
+for ac_func in openpty
+do :
+ ac_fn_c_check_func "$LINENO" "openpty" "ac_cv_func_openpty"
+if test "x$ac_cv_func_openpty" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_OPENPTY 1
+_ACEOF
+
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for openpty in -lutil" >&5
+$as_echo_n "checking for openpty in -lutil... " >&6; }
+if ${ac_cv_lib_util_openpty+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lutil $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char openpty ();
+int
+main ()
+{
+return openpty ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_util_openpty=yes
+else
+ ac_cv_lib_util_openpty=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_util_openpty" >&5
+$as_echo "$ac_cv_lib_util_openpty" >&6; }
+if test "x$ac_cv_lib_util_openpty" = xyes; then :
+ $as_echo "#define HAVE_OPENPTY 1" >>confdefs.h
+ LIBS="$LIBS -lutil"
+fi
+
+fi
+done
+
+
+
+
+$as_echo "#define EMBEDDED 1" >>confdefs.h
+
+
+
+# Check whether --with-embedded was given.
+if test "${with_embedded+set}" = set; then :
+ withval=$with_embedded;
+$as_echo "#define EMBEDDED 1" >>confdefs.h
+ EMBEDDED=1
+fi
+
+
+
+# Check whether --with-erlangstorage was given.
+if test "${with_erlangstorage+set}" = set; then :
+ withval=$with_erlangstorage;
+$as_echo "#define ERLANGSTORAGE 1" >>confdefs.h
+ ERLANGSTORAGE=1
+fi
+
+
+# Check whether --enable-igate was given.
+if test "${enable_igate+set}" = set; then :
+ enableval=$enable_igate; if test "${enable_igate}" = no ; then
+
+$as_echo "#define DISABLE_IGATE 1" >>confdefs.h
+
+fi
+fi
+
+
+# Check whether --enable-agwpe was given.
+if test "${enable_agwpe+set}" = set; then :
+ enableval=$enable_agwpe; if test "${enable_agwpe}" != no ; then
+
+$as_echo "#define ENABLE_AGWPE 1" >>confdefs.h
+
+fi
+fi
+
+
+
+
+# Check whether --with-pthread was given.
+if test "${with_pthread+set}" = set; then :
+ withval=$with_pthread;
+$as_echo "#define DISABLE_PTHREAD 1" >>confdefs.h
+ DISABLE_PTHREAD=1
+else
+
+$as_echo "#define ENABLE_PTHREAD 1" >>confdefs.h
+ ENABLE_PTHREAD=1
+fi
+
+
+# Check whether --with-pthreads was given.
+if test "${with_pthreads+set}" = set; then :
+ withval=$with_pthreads;
+$as_echo "#define ENABLE_PTHREAD 1" >>confdefs.h
+ ENABLE_PTHREAD=1
+fi
+
+
+
+
+if test "${ENABLE_PTHREAD}" = "1" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: The --without-pthread option is not set, looking for pthread_create() function." >&5
+$as_echo "The --without-pthread option is not set, looking for pthread_create() function." >&6; }
+ have_pthread=no
+ if test $have_pthread = no; then
+ t_oldLibs="$LIBS"
+ LIBS="$LIBS -pthread"
+ t_oldCflags="$CFLAGS"
+ CFLAGS="$CFLAGS -pthread"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <pthread.h>
+int
+main ()
+{
+pthread_t pt;
+pthread_attr_t pat;
+int rc = pthread_create(&pt, &pat, NULL, NULL)
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ have_pthread=yes;{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Have pthread_create()" >&5
+$as_echo "Have pthread_create()" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: Not have pthread_create()" >&5
+$as_echo "Not have pthread_create()" >&6; }
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+ if test $have_pthread = no ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: Failure at HAVE_PTHREAD_CREATE" >&5
+$as_echo "Failure at HAVE_PTHREAD_CREATE" >&6; }
+ else
+
+$as_echo "#define HAVE_PTHREAD_CREATE 1" >>confdefs.h
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: Success at HAVE_PTHREAD_CREATE" >&5
+$as_echo "Success at HAVE_PTHREAD_CREATE" >&6; }
+ LIBPTHREAD="-pthread"
+ CCPTHREAD="-pthread"
+ fi
+ LIBS="$t_oldLibs"
+ CFLAGS="$t_oldCflags"
+ fi
+ if test $have_pthread = no; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: Failure at HAVE_PTHREAD_CREATE, trying second way." >&5
+$as_echo "Failure at HAVE_PTHREAD_CREATE, trying second way." >&6; }
+ t_oldLibs="$LIBS"
+ LIBS="$LIBS -lpthread"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <pthread.h>
+int
+main ()
+{
+pthread_t pt;
+pthread_attr_t pat;
+int rc = pthread_create(&pt, &pat, NULL, NULL)
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ have_pthread=yes;{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Have pthread_create()" >&5
+$as_echo "Have pthread_create()" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: Not have pthread_create()" >&5
+$as_echo "Not have pthread_create()" >&6; }
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+ if test $have_pthread = no ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: Failure at HAVE_PTHREAD_CREATE" >&5
+$as_echo "Failure at HAVE_PTHREAD_CREATE" >&6; }
+ else
+
+$as_echo "#define HAVE_PTHREAD_CREATE 1" >>confdefs.h
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: Success at HAVE_PTHREAD_CREATE" >&5
+$as_echo "Success at HAVE_PTHREAD_CREATE" >&6; }
+ LIBPTHREAD="-lpthread"
+ CCPTHREAD=""
+ fi
+ LIBS="$t_oldLibs"
+ CFLAGS="$t_oldCflags"
+ fi
+ if test $have_pthread = no; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: Still failure at HAVE_PTHREAD_CREATE, Run out of ways to set it up." >&5
+$as_echo "Still failure at HAVE_PTHREAD_CREATE, Run out of ways to set it up." >&6; }
+ fi
+fi
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing clock_gettime" >&5
+$as_echo_n "checking for library containing clock_gettime... " >&6; }
+if ${ac_cv_search_clock_gettime+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char clock_gettime ();
+int
+main ()
+{
+return clock_gettime ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' rt; do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_search_clock_gettime=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext
+ if ${ac_cv_search_clock_gettime+:} false; then :
+ break
+fi
+done
+if ${ac_cv_search_clock_gettime+:} false; then :
+
+else
+ ac_cv_search_clock_gettime=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_clock_gettime" >&5
+$as_echo "$ac_cv_search_clock_gettime" >&6; }
+ac_res=$ac_cv_search_clock_gettime
+if test "$ac_res" != no; then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+
+$as_echo "#define HAVE_CLOCK_GETTIME 1" >>confdefs.h
+
+
+fi
+
+
+if test "$ac_cv_search_clock_gettime" = "-lrt"; then
+ LIBRT="-lrt"
+fi
+
+
+for ac_func in getaddrinfo
+do :
+ ac_fn_c_check_func "$LINENO" "getaddrinfo" "ac_cv_func_getaddrinfo"
+if test "x$ac_cv_func_getaddrinfo" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_GETADDRINFO 1
+_ACEOF
+
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for getaddrinfo in -lnsl" >&5
+$as_echo_n "checking for getaddrinfo in -lnsl... " >&6; }
+if ${ac_cv_lib_nsl_getaddrinfo+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnsl $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char getaddrinfo ();
+int
+main ()
+{
+return getaddrinfo ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nsl_getaddrinfo=yes
+else
+ ac_cv_lib_nsl_getaddrinfo=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nsl_getaddrinfo" >&5
+$as_echo "$ac_cv_lib_nsl_getaddrinfo" >&6; }
+if test "x$ac_cv_lib_nsl_getaddrinfo" = xyes; then :
+ LIBGETADDRINFO="-lnsl"
+fi
+
+fi
+done
+
+
+
+#
+# We check for various libraries
+# - SysVr4 style of "-lsocket" at first (unless in libc)
+# The hallmark is connect() routine (we presume)
+#
+ac_cv_libsocket_both=1
+ac_fn_c_check_func "$LINENO" "connect" "ac_cv_func_connect"
+if test "x$ac_cv_func_connect" = xyes; then :
+ ac_cv_libsocket_both=0
+fi
+
+ac_fn_c_check_func "$LINENO" "gethostbyname" "ac_cv_func_gethostbyname"
+if test "x$ac_cv_func_gethostbyname" = xyes; then :
+ ac_cv_libsocket_both=0
+fi
+
+if test "$ac_cv_libsocket_both" = 1 ; then
+ # Check cache
+ if test "$ac_cv_func_socket_lxnet" = yes ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: need -lxnet library (cached)" >&5
+$as_echo "need -lxnet library (cached)" >&6; }
+ LIBSOCKET="-lnsl -lsocket -lxnet"
+ else
+ if test "$ac_cv_func_socket_lsocket" = yes ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: need -lsocket library (cached)" >&5
+$as_echo "need -lsocket library (cached)" >&6; }
+ LIBSOCKET="-lsocket"
+ if test "$ac_cv_func_gethostbyname_lnsl" = yes ; then
+ LIBSOCKET="-lnsl -lsocket"
+ fi
+ else
+ # Well, will this work ? SysVR4, but not Sun Solaris ?
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for connect in -lxnet" >&5
+$as_echo_n "checking for connect in -lxnet... " >&6; }
+if ${ac_cv_lib_xnet_connect+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lxnet $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char connect ();
+int
+main ()
+{
+return connect ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_xnet_connect=yes
+else
+ ac_cv_lib_xnet_connect=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_xnet_connect" >&5
+$as_echo "$ac_cv_lib_xnet_connect" >&6; }
+if test "x$ac_cv_lib_xnet_connect" = xyes; then :
+ LIBSOCKET="-lnsl -lsocket -lxnet"
+ ac_cv_func_socket_lsocket=no
+ ac_cv_func_socket_lxnet=yes
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for connect in -lsocket" >&5
+$as_echo_n "checking for connect in -lsocket... " >&6; }
+if ${ac_cv_lib_socket_connect+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lsocket $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char connect ();
+int
+main ()
+{
+return connect ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_socket_connect=yes
+else
+ ac_cv_lib_socket_connect=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_socket_connect" >&5
+$as_echo "$ac_cv_lib_socket_connect" >&6; }
+if test "x$ac_cv_lib_socket_connect" = xyes; then :
+ LIBSOCKET="-lsocket"
+ ac_cv_func_socket_lsocket=yes
+else
+ ac_cv_func_socket_lsocket=no
+fi
+
+ if test "$ac_cv_func_socket_lsocket" = yes ; then
+ t_oldLibs="$LIBS"
+ LIBS="$LIBS -lsocket $LIBRESOLV"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+gethostbyname();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+
+else
+
+ LIBS="$LIBS -lnsl" # Add this Solaris library..
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+gethostbyname();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+
+ LIBSOCKET="-lsocket -lnsl"
+ ac_cv_func_gethostbyname_lnsl=yes
+
+else
+
+ as_fn_error $? "Weird, '$LIBS' not enough to find gethostnyname() ?!" "$LINENO" 5
+
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+ LIBS="$t_oldLibs"
+ fi
+
+fi
+
+ fi
+ fi
+fi
+
+if test "x$LIBRESOLV" = "x"; then
+ # Ok, No -lresolv, is this enough for the _res to appear ?
+ t_oldLibs="$LIBS"
+ LIBS="$LIBS $LIBSOCKET"
+ ac_cv_var__res_options=no
+ # This following is for IRIX6.4, and I sincerely hope it
+ # will not fail on other systems... It did! It did!
+ # Many systems don't have idemponent headers, they need specific
+ # includes before latter ones, or the latter ones won't be successful...
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+int
+main ()
+{
+_res.options = RES_INIT;
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_var__res_options=yes
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext;
+ if test "$ac_cv_var__res_options" != "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: Can't do without -lresolv to link resolver's _res.options" >&5
+$as_echo "Can't do without -lresolv to link resolver's _res.options" >&6; }
+ LIBS="$LIBS -lresolv"
+ fi
+ LIBS="$t_oldLibs"
+fi
+
+# See about the routines that possibly exist at the libraries..
+LIBS="$t_oldLibs $LIBSOCKET"
+for ac_func in socket socketpair
+do :
+ as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
+ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
+if eval test \"x\$"$as_ac_var"\" = x"yes"; then :
+ cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+done
+
+LIBS="$t_oldLibs"
+
+if test "$ac_cv_func_socket" = no -a "$LIBSOCKET" != ""; then
+ LIBS="$LIBS $LIBSOCKET"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+socket();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_func_socket=yes
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+ if test $ac_cv_func_socket = yes; then
+ $as_echo "#define HAVE_SOCKET 1" >>confdefs.h
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: Has socket() when using $LIBSOCKET" >&5
+$as_echo "Has socket() when using $LIBSOCKET" >&6; }
+ fi
+ LIBS="$t_oldLibs"
+fi
+if test "$ac_cv_func_socketpair" = no -a "$LIBSOCKET" != ""; then
+ LIBS="$LIBS $LIBSOCKET"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+socketpair();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_func_socketpair=yes
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+ if test $ac_cv_func_socketpair = yes; then
+ $as_echo "#define HAVE_SOCKETPAIR 1" >>confdefs.h
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: Has socketpair() when using $LIBSOCKET" >&5
+$as_echo "Has socketpair() when using $LIBSOCKET" >&6; }
+ fi
+ LIBS="$t_oldLibs"
+fi
+
+
+# Check whether --with-openssl was given.
+if test "${with_openssl+set}" = set; then :
+ withval=$with_openssl; with_openssl=$withval
+else
+ with_openssl=no
+fi
+
+
+
+if test "$with_openssl" != "no" ; then
+ for ac_header in openssl/ssl.h
+do :
+ ac_fn_c_check_header_mongrel "$LINENO" "openssl/ssl.h" "ac_cv_header_openssl_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_openssl_ssl_h" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_OPENSSL_SSL_H 1
+_ACEOF
+
+fi
+
+done
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing TLSv1_server_method" >&5
+$as_echo_n "checking for library containing TLSv1_server_method... " >&6; }
+if ${ac_cv_search_TLSv1_server_method+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char TLSv1_server_method ();
+int
+main ()
+{
+return TLSv1_server_method ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' ssl; do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_search_TLSv1_server_method=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext
+ if ${ac_cv_search_TLSv1_server_method+:} false; then :
+ break
+fi
+done
+if ${ac_cv_search_TLSv1_server_method+:} false; then :
+
+else
+ ac_cv_search_TLSv1_server_method=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_TLSv1_server_method" >&5
+$as_echo "$ac_cv_search_TLSv1_server_method" >&6; }
+ac_res=$ac_cv_search_TLSv1_server_method
+if test "$ac_res" != no; then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+$as_echo "#define HAVE_TLSV1_SERVER_METHOD 1" >>confdefs.h
+
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing X509_free" >&5
+$as_echo_n "checking for library containing X509_free... " >&6; }
+if ${ac_cv_search_X509_free+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char X509_free ();
+int
+main ()
+{
+return X509_free ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' crypto; do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_search_X509_free=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext
+ if ${ac_cv_search_X509_free+:} false; then :
+ break
+fi
+done
+if ${ac_cv_search_X509_free+:} false; then :
+
+else
+ ac_cv_search_X509_free=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_X509_free" >&5
+$as_echo "$ac_cv_search_X509_free" >&6; }
+ac_res=$ac_cv_search_X509_free
+if test "$ac_res" != no; then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+$as_echo "#define HAVE_X509_FREE 1" >>confdefs.h
+
+fi
+
+
+ if test "$ac_cv_search_TLSv1_server_method" = "-lssl"; then
+ LIBSSL="-lssl"
+ fi
+ if test "$ac_cv_search_X509_free" = "-lcrypto"; then
+ LIBCRYPTO="-lcrypto"
+ fi
+fi
+
+t_vers="`cat VERSION`"
+VERSION_STRING="`cat VERSION`"
+
+t_vers="`cat SVNVERSION`"
+SVNVERSION_STRING="$t_vers"
+
+
+ac_config_files="$ac_config_files Makefile"
+
+cat >confcache <<\_ACEOF
+# This file is a shell script that caches the results of configure
+# tests run on this system so they can be shared between configure
+# scripts and configure runs, see configure's option --config-cache.
+# It is not useful on other systems. If it contains results you don't
+# want to keep, you may remove or edit it.
+#
+# config.status only pays attention to the cache file if you give it
+# the --recheck option to rerun configure.
+#
+# `ac_cv_env_foo' variables (set or unset) will be overridden when
+# loading this file, other *unset* `ac_cv_foo' will be assigned the
+# following values.
+
+_ACEOF
+
+# The following way of writing the cache mishandles newlines in values,
+# but we know of no workaround that is simple, portable, and efficient.
+# So, we kill variables containing newlines.
+# Ultrix sh set writes to stderr and can't be redirected directly,
+# and sets the high bit in the cache file unless we assign to the vars.
+(
+ for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do
+ eval ac_val=\$$ac_var
+ case $ac_val in #(
+ *${as_nl}*)
+ case $ac_var in #(
+ *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+ esac
+ case $ac_var in #(
+ _ | IFS | as_nl) ;; #(
+ BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
+ *) { eval $ac_var=; unset $ac_var;} ;;
+ esac ;;
+ esac
+ done
+
+ (set) 2>&1 |
+ case $as_nl`(ac_space=' '; set) 2>&1` in #(
+ *${as_nl}ac_space=\ *)
+ # `set' does not quote correctly, so add quotes: double-quote
+ # substitution turns \\\\ into \\, and sed turns \\ into \.
+ sed -n \
+ "s/'/'\\\\''/g;
+ s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p"
+ ;; #(
+ *)
+ # `set' quotes correctly as required by POSIX, so do not add quotes.
+ sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+ ;;
+ esac |
+ sort
+) |
+ sed '
+ /^ac_cv_env_/b end
+ t clear
+ :clear
+ s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/
+ t end
+ s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/
+ :end' >>confcache
+if diff "$cache_file" confcache >/dev/null 2>&1; then :; else
+ if test -w "$cache_file"; then
+ if test "x$cache_file" != "x/dev/null"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5
+$as_echo "$as_me: updating cache $cache_file" >&6;}
+ if test ! -f "$cache_file" || test -h "$cache_file"; then
+ cat confcache >"$cache_file"
+ else
+ case $cache_file in #(
+ */* | ?:*)
+ mv -f confcache "$cache_file"$$ &&
+ mv -f "$cache_file"$$ "$cache_file" ;; #(
+ *)
+ mv -f confcache "$cache_file" ;;
+ esac
+ fi
+ fi
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5
+$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;}
+ fi
+fi
+rm -f confcache
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+DEFS=-DHAVE_CONFIG_H
+
+ac_libobjs=
+ac_ltlibobjs=
+U=
+for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue
+ # 1. Remove the extension, and $U if already installed.
+ ac_script='s/\$U\././;s/\.o$//;s/\.obj$//'
+ ac_i=`$as_echo "$ac_i" | sed "$ac_script"`
+ # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR
+ # will be set to the directory where LIBOBJS objects are built.
+ as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext"
+ as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo'
+done
+LIBOBJS=$ac_libobjs
+
+LTLIBOBJS=$ac_ltlibobjs
+
+
+
+
+: "${CONFIG_STATUS=./config.status}"
+ac_write_fail=0
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files $CONFIG_STATUS"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5
+$as_echo "$as_me: creating $CONFIG_STATUS" >&6;}
+as_write_fail=0
+cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1
+#! $SHELL
+# Generated by $as_me.
+# Run this file to recreate the current configuration.
+# Compiler output produced by configure, useful for debugging
+# configure, is in config.log if it exists.
+
+debug=false
+ac_cs_recheck=false
+ac_cs_silent=false
+
+SHELL=\${CONFIG_SHELL-$SHELL}
+export SHELL
+_ASEOF
+cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1
+## -------------------- ##
+## M4sh Initialization. ##
+## -------------------- ##
+
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
+ emulate sh
+ NULLCMD=:
+ # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+ setopt NO_GLOB_SUBST
+else
+ case `(set -o) 2>/dev/null` in #(
+ *posix*) :
+ set -o posix ;; #(
+ *) :
+ ;;
+esac
+fi
+
+
+as_nl='
+'
+export as_nl
+# Printing a long string crashes Solaris 7 /usr/bin/printf.
+as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
+# Prefer a ksh shell builtin over an external printf program on Solaris,
+# but without wasting forks for bash or zsh.
+if test -z "$BASH_VERSION$ZSH_VERSION" \
+ && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
+ as_echo='print -r --'
+ as_echo_n='print -rn --'
+elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
+ as_echo='printf %s\n'
+ as_echo_n='printf %s'
+else
+ if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
+ as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
+ as_echo_n='/usr/ucb/echo -n'
+ else
+ as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
+ as_echo_n_body='eval
+ arg=$1;
+ case $arg in #(
+ *"$as_nl"*)
+ expr "X$arg" : "X\\(.*\\)$as_nl";
+ arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
+ esac;
+ expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
+ '
+ export as_echo_n_body
+ as_echo_n='sh -c $as_echo_n_body as_echo'
+ fi
+ export as_echo_body
+ as_echo='sh -c $as_echo_body as_echo'
+fi
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+ PATH_SEPARATOR=:
+ (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
+ (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
+ PATH_SEPARATOR=';'
+ }
+fi
+
+
+# IFS
+# We need space, tab and new line, in precisely that order. Quoting is
+# there to prevent editors from complaining about space-tab.
+# (If _AS_PATH_WALK were called with IFS unset, it would disable word
+# splitting by setting IFS to empty value.)
+IFS=" "" $as_nl"
+
+# Find who we are. Look in the path if we contain no directory separator.
+as_myself=
+case $0 in #((
+ *[\\/]* ) as_myself=$0 ;;
+ *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+ as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+ $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+ exit 1
+fi
+
+# Unset variables that we do not need and which cause bugs (e.g. in
+# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1"
+# suppresses any "Segmentation fault" message there. '((' could
+# trigger a bug in pdksh 5.2.14.
+for as_var in BASH_ENV ENV MAIL MAILPATH
+do eval test x\${$as_var+set} = xset \
+ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
+done
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+LC_ALL=C
+export LC_ALL
+LANGUAGE=C
+export LANGUAGE
+
+# CDPATH.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+
+# as_fn_error STATUS ERROR [LINENO LOG_FD]
+# ----------------------------------------
+# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
+# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
+# script with STATUS, using 1 if that was 0.
+as_fn_error ()
+{
+ as_status=$1; test $as_status -eq 0 && as_status=1
+ if test "$4"; then
+ as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
+ fi
+ $as_echo "$as_me: error: $2" >&2
+ as_fn_exit $as_status
+} # as_fn_error
+
+
+# as_fn_set_status STATUS
+# -----------------------
+# Set $? to STATUS, without forking.
+as_fn_set_status ()
+{
+ return $1
+} # as_fn_set_status
+
+# as_fn_exit STATUS
+# -----------------
+# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
+as_fn_exit ()
+{
+ set +e
+ as_fn_set_status $1
+ exit $1
+} # as_fn_exit
+
+# as_fn_unset VAR
+# ---------------
+# Portably unset VAR.
+as_fn_unset ()
+{
+ { eval $1=; unset $1;}
+}
+as_unset=as_fn_unset
+# as_fn_append VAR VALUE
+# ----------------------
+# Append the text in VALUE to the end of the definition contained in VAR. Take
+# advantage of any shell optimizations that allow amortized linear growth over
+# repeated appends, instead of the typical quadratic growth present in naive
+# implementations.
+if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
+ eval 'as_fn_append ()
+ {
+ eval $1+=\$2
+ }'
+else
+ as_fn_append ()
+ {
+ eval $1=\$$1\$2
+ }
+fi # as_fn_append
+
+# as_fn_arith ARG...
+# ------------------
+# Perform arithmetic evaluation on the ARGs, and store the result in the
+# global $as_val. Take advantage of shells that can avoid forks. The arguments
+# must be portable across $(()) and expr.
+if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
+ eval 'as_fn_arith ()
+ {
+ as_val=$(( $* ))
+ }'
+else
+ as_fn_arith ()
+ {
+ as_val=`expr "$@" || test $? -eq 1`
+ }
+fi # as_fn_arith
+
+
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+ test "X`expr 00001 : '.*\(...\)'`" = X001; then
+ as_expr=expr
+else
+ as_expr=false
+fi
+
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
+ as_basename=basename
+else
+ as_basename=false
+fi
+
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+ as_dirname=dirname
+else
+ as_dirname=false
+fi
+
+as_me=`$as_basename -- "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+ X"$0" : 'X\(//\)$' \| \
+ X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X/"$0" |
+ sed '/^.*\/\([^/][^/]*\)\/*$/{
+ s//\1/
+ q
+ }
+ /^X\/\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\/\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in #(((((
+-n*)
+ case `echo 'xy\c'` in
+ *c*) ECHO_T=' ';; # ECHO_T is single tab character.
+ xy) ECHO_C='\c';;
+ *) echo `echo ksh88 bug on AIX 6.1` > /dev/null
+ ECHO_T=' ';;
+ esac;;
+*)
+ ECHO_N='-n';;
+esac
+
+rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+ rm -f conf$$.dir/conf$$.file
+else
+ rm -f conf$$.dir
+ mkdir conf$$.dir 2>/dev/null
+fi
+if (echo >conf$$.file) 2>/dev/null; then
+ if ln -s conf$$.file conf$$ 2>/dev/null; then
+ as_ln_s='ln -s'
+ # ... but there are two gotchas:
+ # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+ # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+ # In both cases, we have to default to `cp -pR'.
+ ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
+ as_ln_s='cp -pR'
+ elif ln conf$$.file conf$$ 2>/dev/null; then
+ as_ln_s=ln
+ else
+ as_ln_s='cp -pR'
+ fi
+else
+ as_ln_s='cp -pR'
+fi
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
+
+
+# as_fn_mkdir_p
+# -------------
+# Create "$as_dir" as a directory, including parents if necessary.
+as_fn_mkdir_p ()
+{
+
+ case $as_dir in #(
+ -*) as_dir=./$as_dir;;
+ esac
+ test -d "$as_dir" || eval $as_mkdir_p || {
+ as_dirs=
+ while :; do
+ case $as_dir in #(
+ *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
+ *) as_qdir=$as_dir;;
+ esac
+ as_dirs="'$as_qdir' $as_dirs"
+ as_dir=`$as_dirname -- "$as_dir" ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$as_dir" : 'X\(//\)[^/]' \| \
+ X"$as_dir" : 'X\(//\)$' \| \
+ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_dir" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)[^/].*/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+ test -d "$as_dir" && break
+ done
+ test -z "$as_dirs" || eval "mkdir $as_dirs"
+ } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
+
+
+} # as_fn_mkdir_p
+if mkdir -p . 2>/dev/null; then
+ as_mkdir_p='mkdir -p "$as_dir"'
+else
+ test -d ./-p && rmdir ./-p
+ as_mkdir_p=false
+fi
+
+
+# as_fn_executable_p FILE
+# -----------------------
+# Test if FILE is an executable regular file.
+as_fn_executable_p ()
+{
+ test -f "$1" && test -x "$1"
+} # as_fn_executable_p
+as_test_x='test -x'
+as_executable_p=as_fn_executable_p
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+exec 6>&1
+## ----------------------------------- ##
+## Main body of $CONFIG_STATUS script. ##
+## ----------------------------------- ##
+_ASEOF
+test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# Save the log message, to keep $0 and so on meaningful, and to
+# report actual input values of CONFIG_FILES etc. instead of their
+# values after options handling.
+ac_log="
+This file was extended by $as_me, which was
+generated by GNU Autoconf 2.69. Invocation command line was
+
+ CONFIG_FILES = $CONFIG_FILES
+ CONFIG_HEADERS = $CONFIG_HEADERS
+ CONFIG_LINKS = $CONFIG_LINKS
+ CONFIG_COMMANDS = $CONFIG_COMMANDS
+ $ $0 $@
+
+on `(hostname || uname -n) 2>/dev/null | sed 1q`
+"
+
+_ACEOF
+
+case $ac_config_files in *"
+"*) set x $ac_config_files; shift; ac_config_files=$*;;
+esac
+
+case $ac_config_headers in *"
+"*) set x $ac_config_headers; shift; ac_config_headers=$*;;
+esac
+
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+# Files that config.status was made for.
+config_files="$ac_config_files"
+config_headers="$ac_config_headers"
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+ac_cs_usage="\
+\`$as_me' instantiates files and other configuration actions
+from templates according to the current configuration. Unless the files
+and actions are specified as TAGs, all are instantiated by default.
+
+Usage: $0 [OPTION]... [TAG]...
+
+ -h, --help print this help, then exit
+ -V, --version print version number and configuration settings, then exit
+ --config print configuration, then exit
+ -q, --quiet, --silent
+ do not print progress messages
+ -d, --debug don't remove temporary files
+ --recheck update $as_me by reconfiguring in the same conditions
+ --file=FILE[:TEMPLATE]
+ instantiate the configuration file FILE
+ --header=FILE[:TEMPLATE]
+ instantiate the configuration header FILE
+
+Configuration files:
+$config_files
+
+Configuration headers:
+$config_headers
+
+Report bugs to the package provider."
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
+ac_cs_version="\\
+config.status
+configured by $0, generated by GNU Autoconf 2.69,
+ with options \\"\$ac_cs_config\\"
+
+Copyright (C) 2012 Free Software Foundation, Inc.
+This config.status script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it."
+
+ac_pwd='$ac_pwd'
+srcdir='$srcdir'
+test -n "\$AWK" || AWK=awk
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# The default lists apply if the user does not specify any file.
+ac_need_defaults=:
+while test $# != 0
+do
+ case $1 in
+ --*=?*)
+ ac_option=`expr "X$1" : 'X\([^=]*\)='`
+ ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'`
+ ac_shift=:
+ ;;
+ --*=)
+ ac_option=`expr "X$1" : 'X\([^=]*\)='`
+ ac_optarg=
+ ac_shift=:
+ ;;
+ *)
+ ac_option=$1
+ ac_optarg=$2
+ ac_shift=shift
+ ;;
+ esac
+
+ case $ac_option in
+ # Handling of the options.
+ -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+ ac_cs_recheck=: ;;
+ --version | --versio | --versi | --vers | --ver | --ve | --v | -V )
+ $as_echo "$ac_cs_version"; exit ;;
+ --config | --confi | --conf | --con | --co | --c )
+ $as_echo "$ac_cs_config"; exit ;;
+ --debug | --debu | --deb | --de | --d | -d )
+ debug=: ;;
+ --file | --fil | --fi | --f )
+ $ac_shift
+ case $ac_optarg in
+ *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
+ '') as_fn_error $? "missing file argument" ;;
+ esac
+ as_fn_append CONFIG_FILES " '$ac_optarg'"
+ ac_need_defaults=false;;
+ --header | --heade | --head | --hea )
+ $ac_shift
+ case $ac_optarg in
+ *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
+ esac
+ as_fn_append CONFIG_HEADERS " '$ac_optarg'"
+ ac_need_defaults=false;;
+ --he | --h)
+ # Conflict between --help and --header
+ as_fn_error $? "ambiguous option: \`$1'
+Try \`$0 --help' for more information.";;
+ --help | --hel | -h )
+ $as_echo "$ac_cs_usage"; exit ;;
+ -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+ | -silent | --silent | --silen | --sile | --sil | --si | --s)
+ ac_cs_silent=: ;;
+
+ # This is an error.
+ -*) as_fn_error $? "unrecognized option: \`$1'
+Try \`$0 --help' for more information." ;;
+
+ *) as_fn_append ac_config_targets " $1"
+ ac_need_defaults=false ;;
+
+ esac
+ shift
+done
+
+ac_configure_extra_args=
+
+if $ac_cs_silent; then
+ exec 6>/dev/null
+ ac_configure_extra_args="$ac_configure_extra_args --silent"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+if \$ac_cs_recheck; then
+ set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion
+ shift
+ \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6
+ CONFIG_SHELL='$SHELL'
+ export CONFIG_SHELL
+ exec "\$@"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+exec 5>>config.log
+{
+ echo
+ sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX
+## Running $as_me. ##
+_ASBOX
+ $as_echo "$ac_log"
+} >&5
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+
+# Handling of arguments.
+for ac_config_target in $ac_config_targets
+do
+ case $ac_config_target in
+ "config.h") CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;;
+ "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;;
+
+ *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
+ esac
+done
+
+
+# If the user did not use the arguments to specify the items to instantiate,
+# then the envvar interface is used. Set only those that are not.
+# We use the long form for the default assignment because of an extremely
+# bizarre bug on SunOS 4.1.3.
+if $ac_need_defaults; then
+ test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files
+ test "${CONFIG_HEADERS+set}" = set || CONFIG_HEADERS=$config_headers
+fi
+
+# Have a temporary directory for convenience. Make it in the build tree
+# simply because there is no reason against having it here, and in addition,
+# creating and moving files from /tmp can sometimes cause problems.
+# Hook for its removal unless debugging.
+# Note that there is a small window in which the directory will not be cleaned:
+# after its creation but before its name has been assigned to `$tmp'.
+$debug ||
+{
+ tmp= ac_tmp=
+ trap 'exit_status=$?
+ : "${ac_tmp:=$tmp}"
+ { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status
+' 0
+ trap 'as_fn_exit 1' 1 2 13 15
+}
+# Create a (secure) tmp directory for tmp files.
+
+{
+ tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` &&
+ test -d "$tmp"
+} ||
+{
+ tmp=./conf$$-$RANDOM
+ (umask 077 && mkdir "$tmp")
+} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5
+ac_tmp=$tmp
+
+# Set up the scripts for CONFIG_FILES section.
+# No need to generate them if there are no CONFIG_FILES.
+# This happens for instance with `./config.status config.h'.
+if test -n "$CONFIG_FILES"; then
+
+
+ac_cr=`echo X | tr X '\015'`
+# On cygwin, bash can eat \r inside `` if the user requested igncr.
+# But we know of no other shell where ac_cr would be empty at this
+# point, so we can use a bashism as a fallback.
+if test "x$ac_cr" = x; then
+ eval ac_cr=\$\'\\r\'
+fi
+ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' </dev/null 2>/dev/null`
+if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then
+ ac_cs_awk_cr='\\r'
+else
+ ac_cs_awk_cr=$ac_cr
+fi
+
+echo 'BEGIN {' >"$ac_tmp/subs1.awk" &&
+_ACEOF
+
+
+{
+ echo "cat >conf$$subs.awk <<_ACEOF" &&
+ echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' &&
+ echo "_ACEOF"
+} >conf$$subs.sh ||
+ as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'`
+ac_delim='%!_!# '
+for ac_last_try in false false false false false :; do
+ . ./conf$$subs.sh ||
+ as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+
+ ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X`
+ if test $ac_delim_n = $ac_delim_num; then
+ break
+ elif $ac_last_try; then
+ as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+ else
+ ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
+ fi
+done
+rm -f conf$$subs.sh
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK &&
+_ACEOF
+sed -n '
+h
+s/^/S["/; s/!.*/"]=/
+p
+g
+s/^[^!]*!//
+:repl
+t repl
+s/'"$ac_delim"'$//
+t delim
+:nl
+h
+s/\(.\{148\}\)..*/\1/
+t more1
+s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/
+p
+n
+b repl
+:more1
+s/["\\]/\\&/g; s/^/"/; s/$/"\\/
+p
+g
+s/.\{148\}//
+t nl
+:delim
+h
+s/\(.\{148\}\)..*/\1/
+t more2
+s/["\\]/\\&/g; s/^/"/; s/$/"/
+p
+b
+:more2
+s/["\\]/\\&/g; s/^/"/; s/$/"\\/
+p
+g
+s/.\{148\}//
+t delim
+' <conf$$subs.awk | sed '
+/^[^""]/{
+ N
+ s/\n//
+}
+' >>$CONFIG_STATUS || ac_write_fail=1
+rm -f conf$$subs.awk
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+_ACAWK
+cat >>"\$ac_tmp/subs1.awk" <<_ACAWK &&
+ for (key in S) S_is_set[key] = 1
+ FS = ""
+
+}
+{
+ line = $ 0
+ nfields = split(line, field, "@")
+ substed = 0
+ len = length(field[1])
+ for (i = 2; i < nfields; i++) {
+ key = field[i]
+ keylen = length(key)
+ if (S_is_set[key]) {
+ value = S[key]
+ line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3)
+ len += length(value) + length(field[++i])
+ substed = 1
+ } else
+ len += 1 + keylen
+ }
+
+ print line
+}
+
+_ACAWK
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then
+ sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g"
+else
+ cat
+fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \
+ || as_fn_error $? "could not setup config files machinery" "$LINENO" 5
+_ACEOF
+
+# VPATH may cause trouble with some makes, so we remove sole $(srcdir),
+# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and
+# trailing colons and then remove the whole line if VPATH becomes empty
+# (actually we leave an empty line to preserve line numbers).
+if test "x$srcdir" = x.; then
+ ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{
+h
+s///
+s/^/:/
+s/[ ]*$/:/
+s/:\$(srcdir):/:/g
+s/:\${srcdir}:/:/g
+s/:@srcdir@:/:/g
+s/^:*//
+s/:*$//
+x
+s/\(=[ ]*\).*/\1/
+G
+s/\n//
+s/^[^=]*=[ ]*$//
+}'
+fi
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+fi # test -n "$CONFIG_FILES"
+
+# Set up the scripts for CONFIG_HEADERS section.
+# No need to generate them if there are no CONFIG_HEADERS.
+# This happens for instance with `./config.status Makefile'.
+if test -n "$CONFIG_HEADERS"; then
+cat >"$ac_tmp/defines.awk" <<\_ACAWK ||
+BEGIN {
+_ACEOF
+
+# Transform confdefs.h into an awk script `defines.awk', embedded as
+# here-document in config.status, that substitutes the proper values into
+# config.h.in to produce config.h.
+
+# Create a delimiter string that does not exist in confdefs.h, to ease
+# handling of long lines.
+ac_delim='%!_!# '
+for ac_last_try in false false :; do
+ ac_tt=`sed -n "/$ac_delim/p" confdefs.h`
+ if test -z "$ac_tt"; then
+ break
+ elif $ac_last_try; then
+ as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5
+ else
+ ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
+ fi
+done
+
+# For the awk script, D is an array of macro values keyed by name,
+# likewise P contains macro parameters if any. Preserve backslash
+# newline sequences.
+
+ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]*
+sed -n '
+s/.\{148\}/&'"$ac_delim"'/g
+t rset
+:rset
+s/^[ ]*#[ ]*define[ ][ ]*/ /
+t def
+d
+:def
+s/\\$//
+t bsnl
+s/["\\]/\\&/g
+s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\
+D["\1"]=" \3"/p
+s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2"/p
+d
+:bsnl
+s/["\\]/\\&/g
+s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\
+D["\1"]=" \3\\\\\\n"\\/p
+t cont
+s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p
+t cont
+d
+:cont
+n
+s/.\{148\}/&'"$ac_delim"'/g
+t clear
+:clear
+s/\\$//
+t bsnlc
+s/["\\]/\\&/g; s/^/"/; s/$/"/p
+d
+:bsnlc
+s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p
+b cont
+' <confdefs.h | sed '
+s/'"$ac_delim"'/"\\\
+"/g' >>$CONFIG_STATUS || ac_write_fail=1
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ for (key in D) D_is_set[key] = 1
+ FS = ""
+}
+/^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ {
+ line = \$ 0
+ split(line, arg, " ")
+ if (arg[1] == "#") {
+ defundef = arg[2]
+ mac1 = arg[3]
+ } else {
+ defundef = substr(arg[1], 2)
+ mac1 = arg[2]
+ }
+ split(mac1, mac2, "(") #)
+ macro = mac2[1]
+ prefix = substr(line, 1, index(line, defundef) - 1)
+ if (D_is_set[macro]) {
+ # Preserve the white space surrounding the "#".
+ print prefix "define", macro P[macro] D[macro]
+ next
+ } else {
+ # Replace #undef with comments. This is necessary, for example,
+ # in the case of _POSIX_SOURCE, which is predefined and required
+ # on some systems where configure will not decide to define it.
+ if (defundef == "undef") {
+ print "/*", prefix defundef, macro, "*/"
+ next
+ }
+ }
+}
+{ print }
+_ACAWK
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+ as_fn_error $? "could not setup config headers machinery" "$LINENO" 5
+fi # test -n "$CONFIG_HEADERS"
+
+
+eval set X " :F $CONFIG_FILES :H $CONFIG_HEADERS "
+shift
+for ac_tag
+do
+ case $ac_tag in
+ :[FHLC]) ac_mode=$ac_tag; continue;;
+ esac
+ case $ac_mode$ac_tag in
+ :[FHL]*:*);;
+ :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;;
+ :[FH]-) ac_tag=-:-;;
+ :[FH]*) ac_tag=$ac_tag:$ac_tag.in;;
+ esac
+ ac_save_IFS=$IFS
+ IFS=:
+ set x $ac_tag
+ IFS=$ac_save_IFS
+ shift
+ ac_file=$1
+ shift
+
+ case $ac_mode in
+ :L) ac_source=$1;;
+ :[FH])
+ ac_file_inputs=
+ for ac_f
+ do
+ case $ac_f in
+ -) ac_f="$ac_tmp/stdin";;
+ *) # Look for the file first in the build tree, then in the source tree
+ # (if the path is not absolute). The absolute path cannot be DOS-style,
+ # because $ac_f cannot contain `:'.
+ test -f "$ac_f" ||
+ case $ac_f in
+ [\\/$]*) false;;
+ *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";;
+ esac ||
+ as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;;
+ esac
+ case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac
+ as_fn_append ac_file_inputs " '$ac_f'"
+ done
+
+ # Let's still pretend it is `configure' which instantiates (i.e., don't
+ # use $as_me), people would be surprised to read:
+ # /* config.h. Generated by config.status. */
+ configure_input='Generated from '`
+ $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g'
+ `' by configure.'
+ if test x"$ac_file" != x-; then
+ configure_input="$ac_file. $configure_input"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5
+$as_echo "$as_me: creating $ac_file" >&6;}
+ fi
+ # Neutralize special characters interpreted by sed in replacement strings.
+ case $configure_input in #(
+ *\&* | *\|* | *\\* )
+ ac_sed_conf_input=`$as_echo "$configure_input" |
+ sed 's/[\\\\&|]/\\\\&/g'`;; #(
+ *) ac_sed_conf_input=$configure_input;;
+ esac
+
+ case $ac_tag in
+ *:-:* | *:-) cat >"$ac_tmp/stdin" \
+ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;;
+ esac
+ ;;
+ esac
+
+ ac_dir=`$as_dirname -- "$ac_file" ||
+$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$ac_file" : 'X\(//\)[^/]' \| \
+ X"$ac_file" : 'X\(//\)$' \| \
+ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$ac_file" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)[^/].*/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+ as_dir="$ac_dir"; as_fn_mkdir_p
+ ac_builddir=.
+
+case "$ac_dir" in
+.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
+*)
+ ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
+ # A ".." for each directory in $ac_dir_suffix.
+ ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
+ case $ac_top_builddir_sub in
+ "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
+ *) ac_top_build_prefix=$ac_top_builddir_sub/ ;;
+ esac ;;
+esac
+ac_abs_top_builddir=$ac_pwd
+ac_abs_builddir=$ac_pwd$ac_dir_suffix
+# for backward compatibility:
+ac_top_builddir=$ac_top_build_prefix
+
+case $srcdir in
+ .) # We are building in place.
+ ac_srcdir=.
+ ac_top_srcdir=$ac_top_builddir_sub
+ ac_abs_top_srcdir=$ac_pwd ;;
+ [\\/]* | ?:[\\/]* ) # Absolute name.
+ ac_srcdir=$srcdir$ac_dir_suffix;
+ ac_top_srcdir=$srcdir
+ ac_abs_top_srcdir=$srcdir ;;
+ *) # Relative name.
+ ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
+ ac_top_srcdir=$ac_top_build_prefix$srcdir
+ ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
+esac
+ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
+
+
+ case $ac_mode in
+ :F)
+ #
+ # CONFIG_FILE
+ #
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# If the template does not know about datarootdir, expand it.
+# FIXME: This hack should be removed a few years after 2.60.
+ac_datarootdir_hack=; ac_datarootdir_seen=
+ac_sed_dataroot='
+/datarootdir/ {
+ p
+ q
+}
+/@datadir@/p
+/@docdir@/p
+/@infodir@/p
+/@localedir@/p
+/@mandir@/p'
+case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in
+*datarootdir*) ac_datarootdir_seen=yes;;
+*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5
+$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;}
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ ac_datarootdir_hack='
+ s&@datadir@&$datadir&g
+ s&@docdir@&$docdir&g
+ s&@infodir@&$infodir&g
+ s&@localedir@&$localedir&g
+ s&@mandir@&$mandir&g
+ s&\\\${datarootdir}&$datarootdir&g' ;;
+esac
+_ACEOF
+
+# Neutralize VPATH when `$srcdir' = `.'.
+# Shell code in configure.ac might set extrasub.
+# FIXME: do we really want to maintain this feature?
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ac_sed_extra="$ac_vpsub
+$extrasub
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+:t
+/@[a-zA-Z_][a-zA-Z_0-9]*@/!b
+s|@configure_input@|$ac_sed_conf_input|;t t
+s&@top_builddir@&$ac_top_builddir_sub&;t t
+s&@top_build_prefix@&$ac_top_build_prefix&;t t
+s&@srcdir@&$ac_srcdir&;t t
+s&@abs_srcdir@&$ac_abs_srcdir&;t t
+s&@top_srcdir@&$ac_top_srcdir&;t t
+s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t
+s&@builddir@&$ac_builddir&;t t
+s&@abs_builddir@&$ac_abs_builddir&;t t
+s&@abs_top_builddir@&$ac_abs_top_builddir&;t t
+$ac_datarootdir_hack
+"
+eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \
+ >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+
+test -z "$ac_datarootdir_hack$ac_datarootdir_seen" &&
+ { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } &&
+ { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \
+ "$ac_tmp/out"`; test -z "$ac_out"; } &&
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined. Please make sure it is defined" >&5
+$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined. Please make sure it is defined" >&2;}
+
+ rm -f "$ac_tmp/stdin"
+ case $ac_file in
+ -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";;
+ *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";;
+ esac \
+ || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+ ;;
+ :H)
+ #
+ # CONFIG_HEADER
+ #
+ if test x"$ac_file" != x-; then
+ {
+ $as_echo "/* $configure_input */" \
+ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs"
+ } >"$ac_tmp/config.h" \
+ || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+ if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5
+$as_echo "$as_me: $ac_file is unchanged" >&6;}
+ else
+ rm -f "$ac_file"
+ mv "$ac_tmp/config.h" "$ac_file" \
+ || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+ fi
+ else
+ $as_echo "/* $configure_input */" \
+ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \
+ || as_fn_error $? "could not create -" "$LINENO" 5
+ fi
+ ;;
+
+
+ esac
+
+done # for ac_tag
+
+
+as_fn_exit 0
+_ACEOF
+ac_clean_files=$ac_clean_files_save
+
+test $ac_write_fail = 0 ||
+ as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5
+
+
+# configure is writing to config.log, and then calls config.status.
+# config.status does its own redirection, appending to config.log.
+# Unfortunately, on DOS this fails, as config.log is still kept open
+# by configure, so config.status won't be able to write to it; its
+# output is simply discarded. So we exec the FD to /dev/null,
+# effectively closing config.log, so it can be properly (re)opened and
+# appended to by config.status. When coming back to configure, we
+# need to make the FD available again.
+if test "$no_create" != yes; then
+ ac_cs_success=:
+ ac_config_status_args=
+ test "$silent" = yes &&
+ ac_config_status_args="$ac_config_status_args --quiet"
+ exec 5>/dev/null
+ $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false
+ exec 5>>config.log
+ # Use ||, not &&, to avoid exiting from the if with $? = 1, which
+ # would make configure fail if this is the last instruction.
+ $ac_cs_success || as_fn_exit 1
+fi
+if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5
+$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;}
+fi
+
diff --git a/configure-stamp b/configure-stamp
new file mode 100644
index 0000000..e69de29
diff --git a/configure.in b/configure.in
new file mode 100644
index 0000000..8442293
--- /dev/null
+++ b/configure.in
@@ -0,0 +1,318 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_INIT
+AC_CONFIG_SRCDIR([aprx.h])
+
+dnl For automake
+VERSION="`cat VERSION`"
+PACKAGE=aprx
+dnl AM_INIT_AUTOMAKE($PACKAGE, $VERSION)
+
+AC_PROG_MAKE_SET
+
+AC_CONFIG_HEADERS([config.h])
+
+dnl Checks for programs.
+AC_PROG_CC
+AC_PROG_GCC_TRADITIONAL
+
+dnl AC_PATH_PROG(LD, ld, ld)dnl
+if test -z "$LD" ; then
+ LD="$CC"
+fi
+AC_SUBST(LD,"$LD")
+
+dnl If on i686, we'll need -march=i686 to get the atomic instructions
+dnl On FreeBSD, the architecture is i386.
+MACHINE="`uname -m`"
+if test "$MACHINE" == "i686" -o "$MACHINE" == "i386"; then
+ CFLAGS_ARCH="-march=i686"
+fi
+OS="`uname`"
+if test "$OS" == "Darwin"; then
+ CFLAGS_ARCH=""
+fi
+AC_SUBST(CFLAGS_ARCH)
+
+dnl Check for GNU make
+AX_CHECK_GNU_MAKE()
+
+
+dnl Check for headers.
+AC_CHECK_HEADERS(time.h sys/time.h stdlib.h stddef.h stdint.h)
+AC_CHECK_HEADERS(string.h strings.h)
+AC_CHECK_HEADERS(pty.h)
+AC_CHECK_HEADERS(pthread.h)
+
+dnl Checks for system headers
+AC_CHECK_HEADERS([alloca.h], AC_DEFINE([HAVE_ALLOCA_H]))
+AC_CHECK_HEADERS([poll.h], AC_DEFINE([HAVE_POLL_H]))
+dnl AC_CHECK_FUNC(ppoll,,[Probably have ppoll of Linux])
+AC_CHECK_HEADERS([sys/epoll.h], AC_DEFINE([HAVE_SYS_EPOLL_H]))
+
+dnl SCTP checks
+AC_CHECK_HEADERS([netinet/sctp.h], AC_DEFINE([HAVE_NETINET_SCTP_H]))
+
+
+
+dnl Check for varargs
+AC_CHECK_HEADERS(stdarg.h varargs.h sys/wait.h)
+AC_FUNC_VPRINTF
+
+
+dnl This group must be after header tests
+
+
+AC_MSG_RESULT([** Using C compiler: $CC])
+AC_MSG_RESULT([** Using CFLAGS: $CFLAGS])
+AC_MSG_RESULT([** Using CPPDEP: $CPPDEP])
+
+AC_C_BIGENDIAN
+dnl AC_INLINE
+AC_CHECK_SIZEOF(void *)
+AC_CHECK_SIZEOF(short)
+AC_CHECK_SIZEOF(int)
+AC_CHECK_SIZEOF(long)
+AC_CHECK_SIZEOF(double)
+
+dnl AC_DEFINE_UNQUOTED(CONFIGURE_CMD,"$0 $ac_configure_args")
+AC_DEFINE_UNQUOTED(CONFIGURE_CMD,
+ "CC='$CC' CFLAGS='$CFLAGS' $0 $ac_configure_args",
+ [Configuration command line])
+
+
+dnl Checks for libraries.
+AC_SUBST(LIBM)
+AC_CHECK_FUNCS(atan2f,,
+ AC_CHECK_LIB(m, atan2f,
+ [LIBM="-lm"]))
+
+AC_CHECK_FUNCS(memchr memrchr gettimeofday)
+
+dnl Checks for library functions.
+AC_CHECK_FUNCS(openpty,,
+ AC_CHECK_LIB(util, openpty,
+ [AC_DEFINE(HAVE_OPENPTY,1,[])] [LIBS="$LIBS -lutil"]))
+
+dnl Check for user options
+
+dnl The "EMBEDDED" is now always on, replaced with --with-erlangstorage
+AC_DEFINE(EMBEDDED,1,[Define for an embedded target])
+
+AC_ARG_WITH(embedded, [ --with-embedded When desiring to target as embedded],
+ [AC_DEFINE(EMBEDDED,1,[Define for an embedded target]) EMBEDDED=1])
+
+AC_ARG_WITH(erlangstorage, [ --with-erlangstorage When desiring a longer term backing storage on erlang datasets. NOT compatible with EMBEDDED, REQUIRES FILESYSTEM!],
+ [AC_DEFINE(ERLANGSTORAGE,1,[Define for a non-embedded system with filesystem based Erlang history storage]) ERLANGSTORAGE=1])
+
+AC_ARG_ENABLE(igate, [ --disable-igate Disable all IGate codes],
+[if test "${enable_igate}" = no ; then
+ AC_DEFINE(DISABLE_IGATE,1,[Define to 1 if you want to disable all IGATE codes.])
+fi])
+
+AC_ARG_ENABLE(agwpe, [ --enable-agwpe Enable AGWPE socket interface code.],
+[if test "${enable_agwpe}" != no ; then
+ AC_DEFINE(ENABLE_AGWPE,1,[Define to 1 if you want to enable AGWPE socket interface.])
+fi])
+
+
+AC_ARG_WITH(pthread, [ --without-pthread When desiring not to use pthread subsystem],
+ [AC_DEFINE(DISABLE_PTHREAD,1,[Define for pthread(3p) disabling]) DISABLE_PTHREAD=1],
+ [AC_DEFINE(ENABLE_PTHREAD,1,[Define for pthread(3p) enabling]) ENABLE_PTHREAD=1])
+AC_ARG_WITH(pthreads, [ --with-pthreads (mistyped pthread) When desiring use pthread subsystem],
+ [AC_DEFINE(ENABLE_PTHREAD,1,[Define for pthread(3p) enabling]) ENABLE_PTHREAD=1])
+
+dnl search for pthread libs and compilation option
+AC_SUBST(LIBPTHREAD)
+AC_SUBST(CCPTHREAD)
+if test "${ENABLE_PTHREAD}" = "1" ; then
+ AC_MSG_RESULT([The --without-pthread option is not set, looking for pthread_create() function.])
+ have_pthread=no
+ if test $have_pthread = no; then
+ t_oldLibs="$LIBS"
+ LIBS="$LIBS -pthread"
+ t_oldCflags="$CFLAGS"
+ CFLAGS="$CFLAGS -pthread"
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <pthread.h>]],[[pthread_t pt;
+pthread_attr_t pat;
+int rc = pthread_create(&pt, &pat, NULL, NULL)]])],
+ [have_pthread=yes;AC_MSG_RESULT([Have pthread_create()])],
+ [AC_MSG_RESULT([Not have pthread_create()])])
+
+ if test $have_pthread = no ; then
+ AC_MSG_RESULT([Failure at HAVE_PTHREAD_CREATE])
+ else
+ AC_DEFINE(HAVE_PTHREAD_CREATE,1,[Have pthread_create() function])
+ AC_MSG_RESULT([Success at HAVE_PTHREAD_CREATE])
+ LIBPTHREAD="-pthread"
+ CCPTHREAD="-pthread"
+ fi
+ LIBS="$t_oldLibs"
+ CFLAGS="$t_oldCflags"
+ fi
+ if test $have_pthread = no; then
+ AC_MSG_RESULT([Failure at HAVE_PTHREAD_CREATE, trying second way.])
+ t_oldLibs="$LIBS"
+ LIBS="$LIBS -lpthread"
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <pthread.h>]],[[pthread_t pt;
+pthread_attr_t pat;
+int rc = pthread_create(&pt, &pat, NULL, NULL)]])],
+ [have_pthread=yes;AC_MSG_RESULT([Have pthread_create()])],
+ [AC_MSG_RESULT([Not have pthread_create()])])
+
+ if test $have_pthread = no ; then
+ AC_MSG_RESULT([Failure at HAVE_PTHREAD_CREATE])
+ else
+ AC_DEFINE(HAVE_PTHREAD_CREATE,1,[Have pthread_create() function])
+ AC_MSG_RESULT([Success at HAVE_PTHREAD_CREATE])
+ LIBPTHREAD="-lpthread"
+ CCPTHREAD=""
+ fi
+ LIBS="$t_oldLibs"
+ CFLAGS="$t_oldCflags"
+ fi
+ if test $have_pthread = no; then
+ AC_MSG_RESULT([Still failure at HAVE_PTHREAD_CREATE, Run out of ways to set it up.])
+ fi
+fi
+
+
+
+dnl Check for clock_gettime, and a library to have it
+AC_SUBST(LIBRT)
+AC_SEARCH_LIBS([clock_gettime], [rt], [
+ AC_DEFINE(HAVE_CLOCK_GETTIME, 1,[Have clock_gettime])
+])
+
+if test "$ac_cv_search_clock_gettime" = "-lrt"; then
+ LIBRT="-lrt"
+fi
+
+dnl Solaris resolver solution:
+AC_SUBST(LIBGETADDRINFO)
+AC_CHECK_FUNCS(getaddrinfo,,
+ AC_CHECK_LIB(nsl, getaddrinfo,
+ [LIBGETADDRINFO="-lnsl"]))
+
+AC_SUBST(LIBSOCKET)dnl
+AC_SUBST(LIBRESOLV)dnl
+
+#
+# We check for various libraries
+# - SysVr4 style of "-lsocket" at first (unless in libc)
+# The hallmark is connect() routine (we presume)
+#
+ac_cv_libsocket_both=1
+AC_CHECK_FUNC(connect, ac_cv_libsocket_both=0)
+AC_CHECK_FUNC(gethostbyname, ac_cv_libsocket_both=0)
+if test "$ac_cv_libsocket_both" = 1 ; then
+ # Check cache
+ if test "$ac_cv_func_socket_lxnet" = yes ; then
+ AC_MSG_RESULT([need -lxnet library (cached)])
+ LIBSOCKET="-lnsl -lsocket -lxnet"
+ else
+ if test "$ac_cv_func_socket_lsocket" = yes ; then
+ AC_MSG_RESULT([need -lsocket library (cached)])
+ LIBSOCKET="-lsocket"
+ if test "$ac_cv_func_gethostbyname_lnsl" = yes ; then
+ LIBSOCKET="-lnsl -lsocket"
+ fi
+ else
+ # Well, will this work ? SysVR4, but not Sun Solaris ?
+ AC_CHECK_LIB(xnet, connect, [LIBSOCKET="-lnsl -lsocket -lxnet"
+ ac_cv_func_socket_lsocket=no
+ ac_cv_func_socket_lxnet=yes],[
+ AC_CHECK_LIB(socket, connect, [LIBSOCKET="-lsocket"
+ ac_cv_func_socket_lsocket=yes],
+ ac_cv_func_socket_lsocket=no)
+ if test "$ac_cv_func_socket_lsocket" = yes ; then
+ t_oldLibs="$LIBS"
+ LIBS="$LIBS -lsocket $LIBRESOLV"
+ AC_TRY_LINK([],[gethostbyname();], ,[
+ LIBS="$LIBS -lnsl" # Add this Solaris library..
+ AC_TRY_LINK([],[gethostbyname();],[
+ LIBSOCKET="-lsocket -lnsl"
+ ac_cv_func_gethostbyname_lnsl=yes
+ ], [
+ AC_MSG_ERROR([Weird, '$LIBS' not enough to find gethostnyname() ?!])
+ ])
+ ])
+ LIBS="$t_oldLibs"
+ fi
+ ])
+ fi
+ fi
+fi
+
+if test "x$LIBRESOLV" = "x"; then
+ # Ok, No -lresolv, is this enough for the _res to appear ?
+ t_oldLibs="$LIBS"
+ LIBS="$LIBS $LIBSOCKET"
+ ac_cv_var__res_options=no
+ # This following is for IRIX6.4, and I sincerely hope it
+ # will not fail on other systems... It did! It did!
+ # Many systems don't have idemponent headers, they need specific
+ # includes before latter ones, or the latter ones won't be successful...
+ AC_TRY_LINK([#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#include <resolv.h>],
+ [_res.options = RES_INIT;],
+ ac_cv_var__res_options=yes);
+ if test "$ac_cv_var__res_options" != "yes"; then
+ AC_MSG_RESULT([Can't do without -lresolv to link resolver's _res.options])
+ LIBS="$LIBS -lresolv"
+ fi
+ LIBS="$t_oldLibs"
+fi
+
+# See about the routines that possibly exist at the libraries..
+LIBS="$t_oldLibs $LIBSOCKET"
+AC_CHECK_FUNCS(socket socketpair)
+LIBS="$t_oldLibs"
+
+if test "$ac_cv_func_socket" = no -a "$LIBSOCKET" != ""; then
+ LIBS="$LIBS $LIBSOCKET"
+ AC_TRY_LINK([],[socket();], ac_cv_func_socket=yes)
+ if test $ac_cv_func_socket = yes; then
+ AC_DEFINE(HAVE_SOCKET)
+ AC_MSG_RESULT([Has socket() when using $LIBSOCKET])
+ fi
+ LIBS="$t_oldLibs"
+fi
+if test "$ac_cv_func_socketpair" = no -a "$LIBSOCKET" != ""; then
+ LIBS="$LIBS $LIBSOCKET"
+ AC_TRY_LINK([],[socketpair();], ac_cv_func_socketpair=yes)
+ if test $ac_cv_func_socketpair = yes; then
+ AC_DEFINE(HAVE_SOCKETPAIR)
+ AC_MSG_RESULT([Has socketpair() when using $LIBSOCKET])
+ fi
+ LIBS="$t_oldLibs"
+fi
+
+dnl Check for openssl
+AC_ARG_WITH(openssl, [ --with-openssl[=DIR] Include OpenSSL support (requires OpenSSL >= 0.9.7)], [with_openssl=$withval], [with_openssl=no])
+AC_SUBST(LIBSSL)
+AC_SUBST(LIBCRYPTO)
+if test "$with_openssl" != "no" ; then
+ AC_CHECK_HEADERS([openssl/ssl.h])
+ AC_SEARCH_LIBS(TLSv1_server_method, ssl, AC_DEFINE(HAVE_TLSV1_SERVER_METHOD, 1, [OpenSSL 0.9.7 or later]))
+ AC_SEARCH_LIBS(X509_free, crypto, AC_DEFINE(HAVE_X509_FREE, 1, [OpenSSL 0.9.7 or later]))
+
+ if test "$ac_cv_search_TLSv1_server_method" = "-lssl"; then
+ LIBSSL="-lssl"
+ fi
+ if test "$ac_cv_search_X509_free" = "-lcrypto"; then
+ LIBCRYPTO="-lcrypto"
+ fi
+fi
+
+dnl Define compilation variables supplying version information
+t_vers="`cat VERSION`"
+AC_SUBST(VERSION_STRING, "`cat VERSION`")
+t_vers="`cat SVNVERSION`"
+AC_SUBST(SVNVERSION_STRING, "$t_vers")
+
+dnl Output files
+AC_CONFIG_FILES([Makefile])
+AC_OUTPUT
diff --git a/coverity-build-submit.sh b/coverity-build-submit.sh
new file mode 100644
index 0000000..a9e9cad
--- /dev/null
+++ b/coverity-build-submit.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+set -e
+
+export PATH=$PATH:~/src/cov-analysis-linux64-6.5.1/bin
+
+make clean
+./configure
+rm -rf cov-int
+cov-build --dir cov-int make
+tar cvfz aprx-coverity.tgz cov-int
+rm -rf cov-int
+
+VERSION="`cat VERSION`"
+PROJECT="Aprx"
+PASSWORD="`cat ~/.covpw`"
+
+echo "Uploading Aprx version $VERSION to Coverity..."
+
+curl --form file=@aprx-coverity.tgz --form project="$PROJECT" \
+ --form password="$PASSWORD" \
+ --form email=oh2mqk at sral.fi \
+ --form version="$VERSION" \
+ --form description="" \
+ http://scan5.coverity.com/cgi-bin/upload.py
+
+rm -f aprx-coverity.tgz
diff --git a/crc.c b/crc.c
new file mode 100644
index 0000000..77cfa51
--- /dev/null
+++ b/crc.c
@@ -0,0 +1,286 @@
+/* **************************************************************** *
+ * *
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ * **************************************************************** */
+
+#include "aprx.h"
+
+
+/*
+ 3 different CRC algorithms:
+ 1) CRC-16
+ 2) CRC-CCITT
+ 3) CRC-FLEXNET
+
+ - - - - - - - - -
+
+ SYMEK et al. have defined a way to run CRC inside KISS frames to
+ verify that the KISS-frame itself is correct:
+
+ http://www.symek.com/g/smack.html
+ http://www.ir3ip.net/iw3fqg/doc/smak.htm
+
+ SMACK variation recycles the top-most bit of the TNC-id nibble, and
+ thus permits up to 8 TNC ports on line. Top-most bit is always one
+ on SMACK frames.
+
+ SMACK runs CRC16 over whole KISS frame buffer, including the CMD byte.
+ The CRC-code is thus _different_ from what will be sent out on radio,
+ the latter being CRC-CCITT (see further below):
+
+ Following CRC16-polynome is used:
+
+ X^16 + X^15 + X^2 + 1
+
+ The CRC-generator is preset to zero.
+
+ Chosen initialize to zero does mean that after a correct packet with a
+ correct checksum is ran thru this CRC, the output checksum will be zero.
+
+ - - - - - - - - -
+
+
+-- ITU-T V.42 / 1993:
+
+ 8.1.1.6.1 16-bit frame check sequence
+
+ The FCS field shall be the sixteen-bit sequence preceding the closing
+ flag. The 16-bit FCS shall be the ones complement of the sum (modulo 2)
+ of
+ a) the remainder of x^k (x^15 + x^14 + x^13 + x^12 +
+ x^11 + x^10 + x^9 + x^8 + x^7 + x^6 + x^5 + x^4 +
+ x^3 + x^2 + x^1 + 1) divided (modulo 2) by the generator
+ polynomial x^16 + x^12 + x^5 + 1, where k is the number
+ of bits in the frame existing between, but not including,
+ the final bit of the opening flag and the first bit of the
+ FCS, excluding bits inserted for transparency; and
+ b) the remainder of the division (modulo 2) by the generator
+ polynomial x^16 + x^12 + x^5 + 1, of the product of x^16
+ by the content of the frame existing between, but not
+ including, the final bit of the opening flag and the first
+ bit of the FCS, excluding bits inserted for transparency.
+
+ As a typical implementation at the transmitter, the initial content
+ of the register of the device computing the remainder of the division
+ is preset to all 1s and is then modified by division by the generator
+ polynomial (as described above) of the address, control and information
+ fields; the ones complement of the resulting remainder is transmitted
+ as the sixteen-bit FCS.
+
+ As a typical implementation at the receiver, the initial content of
+ the register of the device computing the remainder is preset to all 1s.
+ The final remainder, after multiplication by x^16 and then division
+ (modulo 2) by the generator polynomial x^16 + x^12 + x^5 + 1 of the
+ serial incoming protected bits and the FCS, will be
+ “0001 1101 0000 1111” (x^15 through x^0, respectively) in the absence
+ of transmission errors.
+
+
+ Same wording is also on ITU-T X.25 specification.
+
+ - - - - - - - - -
+
+ Where is FLEXNET CRC specification?
+
+
+*/
+
+
+// Polynome 0xA001
+// referred from kiss.c !
+const uint16_t crc16_table[] = {
+ 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241,
+ 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440,
+ 0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40,
+ 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841,
+
+ 0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40,
+ 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41,
+ 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641,
+ 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040,
+
+
+ 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240,
+ 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441,
+ 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41,
+ 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840,
+
+ 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41,
+ 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40,
+ 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640,
+ 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041,
+
+
+ 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240,
+ 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441,
+ 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41,
+ 0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840,
+
+ 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41,
+ 0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40,
+ 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640,
+ 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041,
+
+
+ 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241,
+ 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440,
+ 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40,
+ 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841,
+
+ 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40,
+ 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41,
+ 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641,
+ 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040
+};
+
+uint16_t calc_crc_16(const uint8_t *buf, int n)
+{
+ uint16_t crc = 0;
+
+ while (--n >= 0) {
+ crc = (((crc >> 8) & 0xff) ^
+ crc16_table[(crc ^ *buf++) & 0xFF]);
+ }
+ return crc;
+}
+
+// Return 0 for correct result, anything else for incorrect one
+int check_crc_16(const uint8_t *buf, int n)
+{
+ uint16_t crc = calc_crc_16(buf, n);
+ return (crc != 0); // Correct result is when crc == 0
+}
+
+
+// Polynome 0x8408
+const uint16_t crc_ccitt_table[256] = {
+ 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
+ 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
+ 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
+ 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
+ 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
+ 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
+ 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
+ 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
+ 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
+ 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
+ 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
+ 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
+ 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
+ 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
+ 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
+ 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
+ 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
+ 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
+ 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
+ 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
+ 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
+ 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
+ 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
+ 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
+ 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
+ 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
+ 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
+ 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
+ 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
+ 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
+ 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
+ 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
+};
+
+uint16_t calc_crc_ccitt(uint16_t crc, const uint8_t *buffer, int len)
+{
+ while (len--) {
+ uint8_t c = *buffer++;
+ crc = (crc >> 8) ^ crc_ccitt_table[(crc ^ c) & 0xff];
+ }
+ return crc;
+}
+
+#if 0 // not used!
+// From start of packet to end of packet _including_ 16 bits of FCS
+// Return 0 for OK, other values for errors
+int check_crc_ccitt(const uint8_t *buf, int n) {
+ uint16_t crc = calc_crc_ccitt(0xFFFF, buf, n);
+
+ return (crc != 0xF0B8);
+}
+#endif
+
+
+const uint16_t crc_flex_table[] = {
+ 0x0f87, 0x1e0e, 0x2c95, 0x3d1c, 0x49a3, 0x582a, 0x6ab1, 0x7b38,
+ 0x83cf, 0x9246, 0xa0dd, 0xb154, 0xc5eb, 0xd462, 0xe6f9, 0xf770,
+ 0x1f06, 0x0e8f, 0x3c14, 0x2d9d, 0x5922, 0x48ab, 0x7a30, 0x6bb9,
+ 0x934e, 0x82c7, 0xb05c, 0xa1d5, 0xd56a, 0xc4e3, 0xf678, 0xe7f1,
+
+ 0x2e85, 0x3f0c, 0x0d97, 0x1c1e, 0x68a1, 0x7928, 0x4bb3, 0x5a3a,
+ 0xa2cd, 0xb344, 0x81df, 0x9056, 0xe4e9, 0xf560, 0xc7fb, 0xd672,
+ 0x3e04, 0x2f8d, 0x1d16, 0x0c9f, 0x7820, 0x69a9, 0x5b32, 0x4abb,
+ 0xb24c, 0xa3c5, 0x915e, 0x80d7, 0xf468, 0xe5e1, 0xd77a, 0xc6f3,
+
+
+ 0x4d83, 0x5c0a, 0x6e91, 0x7f18, 0x0ba7, 0x1a2e, 0x28b5, 0x393c,
+ 0xc1cb, 0xd042, 0xe2d9, 0xf350, 0x87ef, 0x9666, 0xa4fd, 0xb574,
+ 0x5d02, 0x4c8b, 0x7e10, 0x6f99, 0x1b26, 0x0aaf, 0x3834, 0x29bd,
+ 0xd14a, 0xc0c3, 0xf258, 0xe3d1, 0x976e, 0x86e7, 0xb47c, 0xa5f5,
+
+ 0x6c81, 0x7d08, 0x4f93, 0x5e1a, 0x2aa5, 0x3b2c, 0x09b7, 0x183e,
+ 0xe0c9, 0xf140, 0xc3db, 0xd252, 0xa6ed, 0xb764, 0x85ff, 0x9476,
+ 0x7c00, 0x6d89, 0x5f12, 0x4e9b, 0x3a24, 0x2bad, 0x1936, 0x08bf,
+ 0xf048, 0xe1c1, 0xd35a, 0xc2d3, 0xb66c, 0xa7e5, 0x957e, 0x84f7,
+
+
+ 0x8b8f, 0x9a06, 0xa89d, 0xb914, 0xcdab, 0xdc22, 0xeeb9, 0xff30,
+ 0x07c7, 0x164e, 0x24d5, 0x355c, 0x41e3, 0x506a, 0x62f1, 0x7378,
+ 0x9b0e, 0x8a87, 0xb81c, 0xa995, 0xdd2a, 0xcca3, 0xfe38, 0xefb1,
+ 0x1746, 0x06cf, 0x3454, 0x25dd, 0x5162, 0x40eb, 0x7270, 0x63f9,
+
+ 0xaa8d, 0xbb04, 0x899f, 0x9816, 0xeca9, 0xfd20, 0xcfbb, 0xde32,
+ 0x26c5, 0x374c, 0x05d7, 0x145e, 0x60e1, 0x7168, 0x43f3, 0x527a,
+ 0xba0c, 0xab85, 0x991e, 0x8897, 0xfc28, 0xeda1, 0xdf3a, 0xceb3,
+ 0x3644, 0x27cd, 0x1556, 0x04df, 0x7060, 0x61e9, 0x5372, 0x42fb,
+
+
+ 0xc98b, 0xd802, 0xea99, 0xfb10, 0x8faf, 0x9e26, 0xacbd, 0xbd34,
+ 0x45c3, 0x544a, 0x66d1, 0x7758, 0x03e7, 0x126e, 0x20f5, 0x317c,
+ 0xd90a, 0xc883, 0xfa18, 0xeb91, 0x9f2e, 0x8ea7, 0xbc3c, 0xadb5,
+ 0x5542, 0x44cb, 0x7650, 0x67d9, 0x1366, 0x02ef, 0x3074, 0x21fd,
+
+ 0xe889, 0xf900, 0xcb9b, 0xda12, 0xaead, 0xbf24, 0x8dbf, 0x9c36,
+ 0x64c1, 0x7548, 0x47d3, 0x565a, 0x22e5, 0x336c, 0x01f7, 0x107e,
+ 0xf808, 0xe981, 0xdb1a, 0xca93, 0xbe2c, 0xafa5, 0x9d3e, 0x8cb7,
+ 0x7440, 0x65c9, 0x5752, 0x46db, 0x3264, 0x23ed, 0x1176, 0x00ff
+};
+
+uint16_t calc_crc_flex(const uint8_t *cp, int size)
+{
+ uint16_t crc = 0xffff;
+
+ while (size--) {
+ uint8_t c = *cp++;
+ crc = (crc << 8) ^ crc_flex_table[((crc >> 8) ^ c) & 0xff];
+ }
+
+ return crc;
+}
+
+#if 0 // not used!
+int check_crc_flex(const uint8_t *cp, int size)
+{
+ uint16_t crc = calc_crc_flex(cp, size);
+
+ if (size < 3)
+ return -1;
+
+ if (crc != 0x7070)
+ return -1;
+
+ return 0;
+}
+#endif
diff --git a/debian/aprx.default b/debian/aprx.default
new file mode 100644
index 0000000..8fe2c08
--- /dev/null
+++ b/debian/aprx.default
@@ -0,0 +1,10 @@
+#
+# STARTAPRX: start aprx on boot. Should be set to "yes" once you have
+# configured aprx.
+#
+STARTAPRX="no"
+
+#
+# Additional options that are passed to the Daemon.
+#
+DAEMON_OPTS=""
diff --git a/debian/aprx.init b/debian/aprx.init
new file mode 100644
index 0000000..0043822
--- /dev/null
+++ b/debian/aprx.init
@@ -0,0 +1,155 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides: aprx
+# Required-Start: $syslog $local_fs
+# Required-Stop: $syslog $local_fs
+# Should-Start: ax25ifs
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: start and stop aprx
+# Description: Monitor and gateway radio amateur APRS radio network datagrams
+### END INIT INFO
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="aprx igate"
+NAME=aprx
+DAEMON=/usr/sbin/$NAME
+DAEMON_ARGS=""
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
+# and status_of_proc is working.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+ # Return
+ # 0 if daemon has been started
+ # 1 if daemon was already running
+ # 2 if daemon could not be started
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
+ || return 1
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
+ $DAEMON_ARGS \
+ || return 2
+ # Add code here, if necessary, that waits for the process to be ready
+ # to handle requests from services started subsequently which depend
+ # on this one. As a last resort, sleep for some time.
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+ # Return
+ # 0 if daemon has been stopped
+ # 1 if daemon was already stopped
+ # 2 if daemon could not be stopped
+ # other if a failure occurred
+ start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
+ RETVAL="$?"
+ [ "$RETVAL" = 2 ] && return 2
+ # Wait for children to finish too if this is a daemon that forks
+ # and if the daemon is only ever run from this initscript.
+ # If the above conditions are not satisfied then add some other code
+ # that waits for the process to drop all resources that could be
+ # needed by services started subsequently. A last resort is to
+ # sleep for some time.
+ start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
+ [ "$?" = 2 ] && return 2
+ # Many daemons don't delete their pidfiles when they exit.
+ rm -f $PIDFILE
+ return "$RETVAL"
+}
+
+
+#
+# Function that sends a SIGHUP to the daemon/service
+#
+do_reload() {
+ #
+ # If the daemon can reload its configuration without
+ # restarting (for example, when it is sent a SIGHUP),
+ # then implement that here.
+ #
+ start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
+ return 0
+}
+
+case "$1" in
+ start)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+ do_start
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ stop)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ status)
+ status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
+ ;;
+ #reload|force-reload)
+ #
+ # If do_reload() is not implemented then leave this commented out
+ # and leave 'force-reload' as an alias for 'restart'.
+ #
+ #log_daemon_msg "Reloading $DESC" "$NAME"
+ #do_reload
+ #log_end_msg $?
+ #;;
+ restart|force-reload)
+ #
+ # If the "reload" option is implemented then remove the
+ # 'force-reload' alias
+ #
+ log_daemon_msg "Restarting $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1)
+ do_start
+ case "$?" in
+ 0) log_end_msg 0 ;;
+ 1) log_end_msg 1 ;; # Old process is still running
+ *) log_end_msg 1 ;; # Failed to start
+ esac
+ ;;
+ *)
+ # Failed to stop
+ log_end_msg 1
+ ;;
+ esac
+ ;;
+ *)
+ #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+ echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
+ exit 3
+ ;;
+esac
+
+:
diff --git a/debian/aprx.postinst.debhelper b/debian/aprx.postinst.debhelper
new file mode 100644
index 0000000..d9381e4
--- /dev/null
+++ b/debian/aprx.postinst.debhelper
@@ -0,0 +1,15 @@
+# Automatically added by dh_installinit
+if [ -x "/etc/init.d/aprx" ]; then
+ update-rc.d aprx defaults >/dev/null
+ if [ -n "$2" ]; then
+ _dh_action=restart
+ else
+ _dh_action=start
+ fi
+ if [ -x "`which invoke-rc.d 2>/dev/null`" ]; then
+ invoke-rc.d aprx $_dh_action || exit $?
+ else
+ /etc/init.d/aprx $_dh_action || exit $?
+ fi
+fi
+# End automatically added section
diff --git a/debian/aprx.postrm.debhelper b/debian/aprx.postrm.debhelper
new file mode 100644
index 0000000..bc814dd
--- /dev/null
+++ b/debian/aprx.postrm.debhelper
@@ -0,0 +1,5 @@
+# Automatically added by dh_installinit
+if [ "$1" = "purge" ] ; then
+ update-rc.d aprx remove >/dev/null || exit $?
+fi
+# End automatically added section
diff --git a/debian/aprx.prerm.debhelper b/debian/aprx.prerm.debhelper
new file mode 100644
index 0000000..0e93bb5
--- /dev/null
+++ b/debian/aprx.prerm.debhelper
@@ -0,0 +1,9 @@
+# Automatically added by dh_installinit
+if [ -x "/etc/init.d/aprx" ] && [ "$1" = remove ]; then
+ if [ -x "`which invoke-rc.d 2>/dev/null`" ]; then
+ invoke-rc.d aprx stop || exit $?
+ else
+ /etc/init.d/aprx stop || exit $?
+ fi
+fi
+# End automatically added section
diff --git a/debian/aprx.substvars b/debian/aprx.substvars
new file mode 100644
index 0000000..36e5a58
--- /dev/null
+++ b/debian/aprx.substvars
@@ -0,0 +1 @@
+shlibs:Depends=libc6 (>= 2.7-1)
diff --git a/debian/aprx/DEBIAN/conffiles b/debian/aprx/DEBIAN/conffiles
new file mode 100644
index 0000000..4c1c3ce
--- /dev/null
+++ b/debian/aprx/DEBIAN/conffiles
@@ -0,0 +1,5 @@
+/etc/apparmor.d/sbin.aprx
+/etc/aprx.conf
+/etc/logrotate.d/aprx
+/etc/default/aprx
+/etc/init.d/aprx
diff --git a/debian/aprx/DEBIAN/control b/debian/aprx/DEBIAN/control
new file mode 100644
index 0000000..b739b92
--- /dev/null
+++ b/debian/aprx/DEBIAN/control
@@ -0,0 +1,19 @@
+Package: aprx
+Version: 2.08.580-1
+Architecture: i386
+Maintainer: HAM APRX release maintenance <aprx at ham>
+Installed-Size: 1048
+Depends: libc6 (>= 2.7-1), openssl
+Section: hamradio
+Priority: extra
+Description: APRS Digipeater and iGate
+ Aprx is an APRS specific Digipeater and iGate.
+ It supports multiple KISS-TNCs on serial ports and listening
+ to any kernel AX.25 network interfaces.
+ .
+ Additional features include a built-in "erlang-monitor" to analyze
+ activity level of radio channels.
+ .
+ This software requires a valid (and unique) ham radio callsign to
+ operate fully and is therefore useful mainly for licensed radio
+ amateurs.
diff --git a/debian/aprx/DEBIAN/md5sums b/debian/aprx/DEBIAN/md5sums
new file mode 100644
index 0000000..282a406
--- /dev/null
+++ b/debian/aprx/DEBIAN/md5sums
@@ -0,0 +1,13 @@
+7c2a0786fee7fee92b86115088853a62 usr/sbin/aprx
+d6a98f1bbd799491f9559fc43e8bf7f6 usr/sbin/aprx-stat
+a9d04507f86642315745ba8f0e76df32 usr/share/man/man8/aprx.8.gz
+9cf04acfcca16e1c5791bcd395052b27 usr/share/man/man8/aprx-stat.8.gz
+673c8ba180344e2d5c22bb75f99809ae usr/share/doc/aprx/README
+09890d56e7d42c48f4bafcff6366b304 usr/share/doc/aprx/LICENSE
+6a4c523ff4e1adba1aaf0b8d3325fb08 usr/share/doc/aprx/ROADMAP
+c994b2fce146b8ef5cda6678ca45b6f2 usr/share/doc/aprx/copyright
+261f41ba62cd3667fa45400cb41c92f9 usr/share/doc/aprx/TODO.gz
+70e5e4e805429add4c0fd5958722e6b3 usr/share/doc/aprx/PROTOCOLS.gz
+5105fa38a3b4f732baea02a62b5c6eb9 usr/share/doc/aprx/aprx.conf.gz
+02cd7c464362015dd31ea4c5e0d8fe2b usr/share/doc/aprx/aprx-complex.conf.gz
+4823ba66d187982a7570635cbed33a79 usr/share/doc/aprx/aprx-manual.pdf.gz
diff --git a/debian/aprx/DEBIAN/postinst b/debian/aprx/DEBIAN/postinst
new file mode 100755
index 0000000..d0406f7
--- /dev/null
+++ b/debian/aprx/DEBIAN/postinst
@@ -0,0 +1,17 @@
+#!/bin/sh
+set -e
+# Automatically added by dh_installinit
+if [ -x "/etc/init.d/aprx" ]; then
+ update-rc.d aprx defaults >/dev/null
+ if [ -n "$2" ]; then
+ _dh_action=restart
+ else
+ _dh_action=start
+ fi
+ if [ -x "`which invoke-rc.d 2>/dev/null`" ]; then
+ invoke-rc.d aprx $_dh_action || exit $?
+ else
+ /etc/init.d/aprx $_dh_action || exit $?
+ fi
+fi
+# End automatically added section
diff --git a/debian/aprx/DEBIAN/postrm b/debian/aprx/DEBIAN/postrm
new file mode 100755
index 0000000..4dd5333
--- /dev/null
+++ b/debian/aprx/DEBIAN/postrm
@@ -0,0 +1,7 @@
+#!/bin/sh
+set -e
+# Automatically added by dh_installinit
+if [ "$1" = "purge" ] ; then
+ update-rc.d aprx remove >/dev/null || exit $?
+fi
+# End automatically added section
diff --git a/debian/aprx/DEBIAN/prerm b/debian/aprx/DEBIAN/prerm
new file mode 100755
index 0000000..f96d6f9
--- /dev/null
+++ b/debian/aprx/DEBIAN/prerm
@@ -0,0 +1,11 @@
+#!/bin/sh
+set -e
+# Automatically added by dh_installinit
+if [ -x "/etc/init.d/aprx" ] && [ "$1" = remove ]; then
+ if [ -x "`which invoke-rc.d 2>/dev/null`" ]; then
+ invoke-rc.d aprx stop || exit $?
+ else
+ /etc/init.d/aprx stop || exit $?
+ fi
+fi
+# End automatically added section
diff --git a/debian/aprx/etc/apparmor.d/sbin.aprx b/debian/aprx/etc/apparmor.d/sbin.aprx
new file mode 100644
index 0000000..00b878f
--- /dev/null
+++ b/debian/aprx/etc/apparmor.d/sbin.aprx
@@ -0,0 +1,17 @@
+#include <tunables/global>
+
+/sbin/aprx {
+ #include <abstractions/base>
+ #include <abstractions/nameservice>
+
+
+ capability setgid,
+ capability setuid,
+ capability sys_chroot,
+
+
+ /etc/aprx.conf r,
+ owner /var/run/aprx.pid rwk,
+ owner /var/run/aprx.state rwk,
+ owner /var/log/aprx/* rwk,
+}
diff --git a/debian/aprx/etc/default/aprx b/debian/aprx/etc/default/aprx
new file mode 100644
index 0000000..8fe2c08
--- /dev/null
+++ b/debian/aprx/etc/default/aprx
@@ -0,0 +1,10 @@
+#
+# STARTAPRX: start aprx on boot. Should be set to "yes" once you have
+# configured aprx.
+#
+STARTAPRX="no"
+
+#
+# Additional options that are passed to the Daemon.
+#
+DAEMON_OPTS=""
diff --git a/debian/aprx/etc/init.d/aprx b/debian/aprx/etc/init.d/aprx
new file mode 100755
index 0000000..0043822
--- /dev/null
+++ b/debian/aprx/etc/init.d/aprx
@@ -0,0 +1,155 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides: aprx
+# Required-Start: $syslog $local_fs
+# Required-Stop: $syslog $local_fs
+# Should-Start: ax25ifs
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: start and stop aprx
+# Description: Monitor and gateway radio amateur APRS radio network datagrams
+### END INIT INFO
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="aprx igate"
+NAME=aprx
+DAEMON=/usr/sbin/$NAME
+DAEMON_ARGS=""
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
+# and status_of_proc is working.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+ # Return
+ # 0 if daemon has been started
+ # 1 if daemon was already running
+ # 2 if daemon could not be started
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
+ || return 1
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
+ $DAEMON_ARGS \
+ || return 2
+ # Add code here, if necessary, that waits for the process to be ready
+ # to handle requests from services started subsequently which depend
+ # on this one. As a last resort, sleep for some time.
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+ # Return
+ # 0 if daemon has been stopped
+ # 1 if daemon was already stopped
+ # 2 if daemon could not be stopped
+ # other if a failure occurred
+ start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
+ RETVAL="$?"
+ [ "$RETVAL" = 2 ] && return 2
+ # Wait for children to finish too if this is a daemon that forks
+ # and if the daemon is only ever run from this initscript.
+ # If the above conditions are not satisfied then add some other code
+ # that waits for the process to drop all resources that could be
+ # needed by services started subsequently. A last resort is to
+ # sleep for some time.
+ start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
+ [ "$?" = 2 ] && return 2
+ # Many daemons don't delete their pidfiles when they exit.
+ rm -f $PIDFILE
+ return "$RETVAL"
+}
+
+
+#
+# Function that sends a SIGHUP to the daemon/service
+#
+do_reload() {
+ #
+ # If the daemon can reload its configuration without
+ # restarting (for example, when it is sent a SIGHUP),
+ # then implement that here.
+ #
+ start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
+ return 0
+}
+
+case "$1" in
+ start)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+ do_start
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ stop)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ status)
+ status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
+ ;;
+ #reload|force-reload)
+ #
+ # If do_reload() is not implemented then leave this commented out
+ # and leave 'force-reload' as an alias for 'restart'.
+ #
+ #log_daemon_msg "Reloading $DESC" "$NAME"
+ #do_reload
+ #log_end_msg $?
+ #;;
+ restart|force-reload)
+ #
+ # If the "reload" option is implemented then remove the
+ # 'force-reload' alias
+ #
+ log_daemon_msg "Restarting $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1)
+ do_start
+ case "$?" in
+ 0) log_end_msg 0 ;;
+ 1) log_end_msg 1 ;; # Old process is still running
+ *) log_end_msg 1 ;; # Failed to start
+ esac
+ ;;
+ *)
+ # Failed to stop
+ log_end_msg 1
+ ;;
+ esac
+ ;;
+ *)
+ #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+ echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
+ exit 3
+ ;;
+esac
+
+:
diff --git a/debian/aprx/etc/logrotate.d/aprx b/debian/aprx/etc/logrotate.d/aprx
new file mode 100644
index 0000000..3665ba2
--- /dev/null
+++ b/debian/aprx/etc/logrotate.d/aprx
@@ -0,0 +1,8 @@
+/var/log/aprx/aprx-rf.log /var/log/aprx/aprx.log /var/log/aprx/dprs.log /var/log/aprx/erlang.log {
+ weekly
+ rotate 4
+ compress
+ missingok
+ notifempty
+ create 644 root adm
+}
diff --git a/debian/aprx/usr/sbin/aprx b/debian/aprx/usr/sbin/aprx
new file mode 100755
index 0000000..b078b09
Binary files /dev/null and b/debian/aprx/usr/sbin/aprx differ
diff --git a/debian/aprx/usr/sbin/aprx-stat b/debian/aprx/usr/sbin/aprx-stat
new file mode 100755
index 0000000..bd8f3bb
Binary files /dev/null and b/debian/aprx/usr/sbin/aprx-stat differ
diff --git a/debian/aprx/usr/share/doc/aprx/LICENSE b/debian/aprx/usr/share/doc/aprx/LICENSE
new file mode 100644
index 0000000..db21adc
--- /dev/null
+++ b/debian/aprx/usr/share/doc/aprx/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2007-2014, Matti Aarnio
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of Matti Aarnio nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/debian/aprx/usr/share/doc/aprx/PROTOCOLS.gz b/debian/aprx/usr/share/doc/aprx/PROTOCOLS.gz
new file mode 100644
index 0000000..a623c2d
Binary files /dev/null and b/debian/aprx/usr/share/doc/aprx/PROTOCOLS.gz differ
diff --git a/debian/aprx/usr/share/doc/aprx/README b/debian/aprx/usr/share/doc/aprx/README
new file mode 100644
index 0000000..8c130bc
--- /dev/null
+++ b/debian/aprx/usr/share/doc/aprx/README
@@ -0,0 +1,86 @@
+
+ APRX v2.08
+
+A multitalented APRS / DPRS / APRSIS "i-gate" with following properties:
+
+ Config file (-f option) default is: /etc/aprx.conf
+ Other runtime options are: -v, -d, -h/-? (verbout, debug and help)
+
+ - Rx-IGate functionality works correctly
+ - Tx-IGate functionality works correctly
+
+ - Can do APRS New-N and generic AX.25 node digipeater functionality
+ with transmitters
+
+ - Has same-channel Viscous Digipeater functionality to not to digipeat
+ at all, if during initial wait period the packet is heard again.
+
+ - Has cross-interface Viscous Digipeater functionality to not to digipeat,
+ if during the initial wait period the packet is heard on destination
+ interface at least once, and at least once from other sources.
+
+ - Can receive data from multiple receivers/modems on local machine
+ serial ports, both classical and USB.
+
+ - Can receive data from remote TCP stream connectable serial ports
+ over the internet.
+
+ - Understands on serial ports (local and remote TCP ones):
+ - several KISS protocol variants, checksummed variants preferred
+ - TNC2 debug style text (Rx-iGate receive only.)
+ - D-STAR data side-channel "D-PRS"
+
+ - Connects with one callsign-ssid pair to APRS-IS core for all
+ received radio ports (the "mycall" parameter), but reports
+ receiving radio port at each Rx-iGated packet
+
+ - Knows that messages with following tokens in VIA fields of the
+ path are not to be relayed into network:
+ RFONLY, NOGATE, TCPIP, TCPXX
+
+ - Knows that following source address prefixes are bogus and thus
+ to be junked at Rx-iGate:
+ WIDE, RELAY, TRACE, TCPIP, TCPXX, NOCALL, N0CALL
+ (Actually these are string prefixes, so any WIDE*-* will block, etc.)
+
+ - Has integrated D-PRS -> APRS/APRSIS Rx-iGate.
+ Can even do D-PRS -> APRS RF conversion.
+ This is experimental quality for "GPS" packets, the "GPS-A" is OK.
+
+ - Does not require machine to have AX.25 protocol support internally!
+
+ - On Linux machine with kernel internal AX.25 protocol support, does
+ listen on internal AX.25 network in promiscuous form, and requires
+ to be running as root to do that. Does not fail to start in case
+ the port fails to open (running as non-root.)
+
+ - Built-in "erlang-monitor" actually counts bytes per time interval
+ (1 min, 10 min, and 20 min) on each receiving interface, including
+ all that feed internal AX.25 network.
+
+ - Telemetry reported erlang data is sent out every 20 minutes, and
+ contains summarized data from 10 minute round-robin memory arrays.
+ These are _not_ sent at exact 10 minutes of wall-clock, but exact
+ 20 minutes from previous telemetry reporting, and first one is sent
+ 20 minutes after program start.
+
+ - Telemetry reported erlang data can be sent also over APRS radio
+ port, but only for ports with valid AX.25 callsigns. See aprx-manual.pdf
+
+ - The netbeacons are distributed timewise more evenly around the interval,
+ and even the interval length is varied at random in between 20 and 30
+ minutes. Number of netbeacons are unlimited, but their minimum transmit
+ interval is 3 seconds making the amount of beacons sendable in 20 minutes
+ to be: 20*60/3 = 400. All will be sent, but the cycle will just take
+ longer.
+
+ - Source code is at SVN repository: http://repo.ham.fi/svn/aprx
+ - A Wiki page of this package: http://wiki.ham.fi/Aprx.en
+ - A google-group for Aprx: http://groups.google.com/group/aprx-software
+
+ - This program is also compilable as "EMBEDDED" target without
+ any statistics buffer required at the system disk (or RAM-disk).
+ See the INSTALL file.
+
+
+by Matti Aarnio - OH2MQK - oh2mqk-at-sral-fi - 2007-2014
diff --git a/debian/aprx/usr/share/doc/aprx/ROADMAP b/debian/aprx/usr/share/doc/aprx/ROADMAP
new file mode 100644
index 0000000..95f70c4
--- /dev/null
+++ b/debian/aprx/usr/share/doc/aprx/ROADMAP
@@ -0,0 +1,26 @@
+ Aprx Roadmap and Future Directions
+
+
+Version 1
+ - APRS Rx-only iGate - Complete, working
+ - channel activity monitoring and telemetry - Complete, working
+
+
+Version 2
+ - Digipeater - Working
+ - Analyze and detect station distance - Working
+ - Radio beacons - Working
+ - Bidirection (Rx/Tx) APRS iGate - Working
+ - DPRS->APRS GW - Working
+
+Version 2+
+
+ - Port to ucLinux - Planned (pthread OK)
+
+ - Port to Windows
+
+ - Automated coverage statistics analyzer, and
+ reporting it via digi node identity beacons.
+ "ALOHA circles"
+
+ - Automated coverage plotting
diff --git a/debian/aprx/usr/share/doc/aprx/TODO.gz b/debian/aprx/usr/share/doc/aprx/TODO.gz
new file mode 100644
index 0000000..848a682
Binary files /dev/null and b/debian/aprx/usr/share/doc/aprx/TODO.gz differ
diff --git a/debian/aprx/usr/share/doc/aprx/aprx-complex.conf.gz b/debian/aprx/usr/share/doc/aprx/aprx-complex.conf.gz
new file mode 100644
index 0000000..331afe6
Binary files /dev/null and b/debian/aprx/usr/share/doc/aprx/aprx-complex.conf.gz differ
diff --git a/debian/aprx/usr/share/doc/aprx/aprx-manual.pdf.gz b/debian/aprx/usr/share/doc/aprx/aprx-manual.pdf.gz
new file mode 100644
index 0000000..238586e
Binary files /dev/null and b/debian/aprx/usr/share/doc/aprx/aprx-manual.pdf.gz differ
diff --git a/debian/aprx/usr/share/doc/aprx/aprx.conf.gz b/debian/aprx/usr/share/doc/aprx/aprx.conf.gz
new file mode 100644
index 0000000..3a2c225
Binary files /dev/null and b/debian/aprx/usr/share/doc/aprx/aprx.conf.gz differ
diff --git a/debian/aprx/usr/share/doc/aprx/copyright b/debian/aprx/usr/share/doc/aprx/copyright
new file mode 100644
index 0000000..322cb19
--- /dev/null
+++ b/debian/aprx/usr/share/doc/aprx/copyright
@@ -0,0 +1 @@
+See LICENSE.
diff --git a/debian/aprx/usr/share/man/man8/aprx-stat.8.gz b/debian/aprx/usr/share/man/man8/aprx-stat.8.gz
new file mode 100644
index 0000000..0bf6968
Binary files /dev/null and b/debian/aprx/usr/share/man/man8/aprx-stat.8.gz differ
diff --git a/debian/aprx/usr/share/man/man8/aprx.8.gz b/debian/aprx/usr/share/man/man8/aprx.8.gz
new file mode 100644
index 0000000..4d3b82c
Binary files /dev/null and b/debian/aprx/usr/share/man/man8/aprx.8.gz differ
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..3b6a4a0
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,12 @@
+aprx (2.08.593-1) unstable; urgency=low
+
+ * See main ChangeLog.
+
+ -- aprx maintainer <arpx at ham> Sun, 06 Apr 2014 02:06:07 +0300
+
+aprx (0.0.0-1) unstable; urgency=low
+
+ * Initial debianization.
+
+ -- Kimmo Jukarainen <oh3gnu at ham> Thu, 3 Jan 2008 20:35:29 +0200
+
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..7ed6ff8
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+5
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..139e156
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,22 @@
+Source: aprx
+Section: hamradio
+Priority: extra
+Maintainer: HAM APRX release maintenance <aprx at ham>
+Build-Depends: debhelper (>= 4), libssl-dev
+Standards-Version: 3.7.2
+
+Package: aprx
+Architecture: any
+Depends: ${shlibs:Depends}, openssl
+Description: APRS Digipeater and iGate
+ Aprx is an APRS specific Digipeater and iGate.
+ It supports multiple KISS-TNCs on serial ports and listening
+ to any kernel AX.25 network interfaces.
+ .
+ Additional features include a built-in "erlang-monitor" to analyze
+ activity level of radio channels.
+ .
+ This software requires a valid (and unique) ham radio callsign to
+ operate fully and is therefore useful mainly for licensed radio
+ amateurs.
+
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..322cb19
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1 @@
+See LICENSE.
diff --git a/debian/dirs b/debian/dirs
new file mode 100644
index 0000000..0ded9b9
--- /dev/null
+++ b/debian/dirs
@@ -0,0 +1,6 @@
+etc/
+etc/apparmor.d
+usr/sbin
+usr/share/man/man8
+var/log/aprx
+var/run
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 0000000..1a08e42
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1,8 @@
+README
+TODO
+PROTOCOLS
+LICENSE
+ROADMAP
+aprx.conf
+aprx-complex.conf
+doc/aprx-manual.pdf
diff --git a/debian/files b/debian/files
new file mode 100644
index 0000000..0d9a581
--- /dev/null
+++ b/debian/files
@@ -0,0 +1 @@
+aprx_2.08.580-1_i386.deb hamradio extra
diff --git a/debian/postinst.off b/debian/postinst.off
new file mode 100644
index 0000000..2c75b45
--- /dev/null
+++ b/debian/postinst.off
@@ -0,0 +1,49 @@
+#!/bin/sh -e
+
+action="$1"
+oldversion="$2"
+
+. /usr/share/debconf/confmodule
+db_version 2.0
+
+umask 022
+
+if [ "$action" != configure ] ; then
+ exit 0
+fi
+
+# functions
+
+setup_aprx_user() {
+ if ! getent passwd aprsc >/dev/null; then
+ echo "Creating user account: 'aprsc'"
+ adduser --quiet --system --no-create-home --home /var/run/aprx --shell /usr/sbin/nologin --group aprx
+ fi
+}
+
+fix_permissions() {
+ :
+ # chown aprx:aprx /opt/aprsc/logs /opt/aprsc/data
+}
+
+apparmor_config() {
+ # Reload AppArmor profile
+ APP_PROFILE="/etc/apparmor.d/sbin.aprx"
+ if [ -f "$APP_PROFILE" ] && aa-status --enabled 2>/dev/null; then
+ echo "Installing apparmor profile..."
+ apparmor_parser -r -T -W "$APP_PROFILE" || true
+ fi
+}
+
+# main
+
+# setup_aprx_user
+# fix_permissions
+apparmor_config
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..8ce7c56
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,96 @@
+#!/usr/bin/make -f
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+CFLAGS = -Wall -g
+
+ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS)))
+ CFLAGS += -O0
+else
+ CFLAGS += -O2
+endif
+
+configure: configure-stamp
+configure-stamp:
+ dh_testdir
+ # Add here commands to configure the package.
+ ./configure --with-pthread --sbindir=/usr/sbin --sysconfdir=/etc \
+ --localstatedir=/var --mandir=/usr/share/man \
+ CC="gcc" \
+ CFLAGS="${CFLAGS}" \
+ AFLAGS="${CFLAGS} --noexecstack" \
+ LDFLAGS="${CFLAGS} -z noexecstack"
+ touch configure-stamp
+
+
+build: build-stamp
+
+build-stamp: configure-stamp
+ dh_testdir
+
+ # Add here commands to compile the package.
+ $(MAKE)
+ #docbook-to-man debian/aprx.sgml > aprx.1
+
+ touch $@
+
+clean:
+ dh_testdir
+ dh_testroot
+ rm -f build-stamp configure-stamp
+
+ # Add here commands to clean up after the build process.
+ -$(MAKE) clean
+ rm -f debian/aprx.logrotate
+
+ dh_clean
+
+install: build
+ dh_testdir
+ dh_testroot
+ dh_clean -k
+ dh_installdirs
+
+ # Add here commands to install the package into debian/aprx.
+ $(MAKE) DESTDIR=$(CURDIR)/debian/aprx logrotate.aprx install
+ cp logrotate.aprx debian/aprx.logrotate
+ install -m 644 apparmor.aprx $(CURDIR)/debian/aprx/etc/apparmor.d/sbin.aprx
+
+# Build architecture-independent files here.
+binary-indep: build install
+# We have nothing to do by default.
+
+# Build architecture-dependent files here.
+binary-arch: build install
+ dh_testdir
+ dh_testroot
+# dh_installchangelogs ChangeLog
+ dh_installdocs
+ dh_installexamples
+# dh_install
+# dh_installmenu
+# dh_installdebconf
+ dh_installlogrotate
+# dh_installemacsen
+# dh_installpam
+# dh_installmime
+# dh_python
+ dh_installinit --restart-after-upgrade
+# dh_installcron
+# dh_installinfo
+ dh_installman
+ dh_link
+# dh_strip
+ dh_compress
+ dh_fixperms
+# dh_perl
+# dh_makeshlibs
+ dh_installdeb
+ dh_shlibdeps
+ dh_gencontrol
+ dh_md5sums
+ dh_builddeb
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install configure
diff --git a/digipeater.c b/digipeater.c
new file mode 100644
index 0000000..329169a
--- /dev/null
+++ b/digipeater.c
@@ -0,0 +1,1867 @@
+/* **************************************************************** *
+ * *
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ * **************************************************************** */
+
+#include "aprx.h"
+
+static int digi_count;
+static struct digipeater **digis;
+
+#define TOKENBUCKET_INTERVAL 5 // 5 seconds per refill.
+ // 60/5 part of "ratelimit" to be max
+ // that token bucket can be filled to.
+
+static struct timeval tokenbucket_timer;
+
+struct viastate {
+ int hopsreq;
+ int hopsdone;
+ int tracereq;
+ int tracedone;
+ int digireq;
+ int digidone;
+
+ int fixthis;
+ int fixall;
+ int probably_heard_direct;
+};
+
+struct digistate {
+ struct viastate v;
+
+ int ax25addrlen;
+ uint8_t ax25addr[90]; // 70 for address, a bit more for "body"
+};
+
+
+#define AX25ADDRMAXLEN 70 // SRC + DEST + 8*VIA (7 bytes each)
+#define AX25ADDRLEN 7
+#define AX25HBIT 0x80
+#define AX25ATERM 0x01
+
+static char * tracewords[] = { "WIDE","TRACE","RELAY" };
+static int tracewordlens[] = { 4, 5, 5 };
+static const struct tracewide default_trace_param = {
+ 4, 4, 1,
+ 3,
+ tracewords,
+ tracewordlens
+};
+static char * widewords[] = { "WIDE","RELAY" };
+static int widewordlens[] = { 4,5 };
+static const struct tracewide default_wide_param = {
+ 4, 4, 0,
+ 2,
+ widewords,
+ widewordlens
+};
+
+static int run_tokenbucket_timers(void);
+
+
+float ratelimitmax = 9999999.9;
+float rateincrementmax = 9999999.9;
+
+
+/*
+ * regex_filter_add() -- adds configured regular expressions
+ * into forbidden patterns list.
+ *
+ * These are actually processed on TNC2 format text line, and not
+ * AX.25 datastream per se.
+ */
+static int regex_filter_add(struct configfile *cf,
+ struct digipeater_source *src,
+ char *param1,
+ char *str)
+{
+ int rc;
+ int groupcode = -1;
+ regex_t re, *rep;
+ char errbuf[2000];
+
+ if (strcmp(param1, "source") == 0) {
+ groupcode = 0;
+ } else if (strcmp(param1, "destination") == 0) {
+ groupcode = 1;
+ } else if (strcmp(param1, "via") == 0) {
+ groupcode = 2;
+ } else if (strcmp(param1, "data") == 0) {
+ groupcode = 3;
+ } else {
+ printf("%s:%d ERROR: Bad RE target: '%s' must be one of: source, destination, via\n",
+ cf->name, cf->linenum, param1);
+ return 1;
+ }
+
+ if (!*str) {
+ printf("%s:%d ERROR: Expected RE pattern missing or a NUL string.\n",
+ cf->name, cf->linenum);
+ return 1; /* Bad input.. */
+ }
+
+ param1 = str;
+ str = config_SKIPTEXT(str, NULL); // Handle quoted string
+ str = config_SKIPSPACE(str);
+
+ memset(&re, 0, sizeof(re));
+ rc = regcomp(&re, param1, REG_EXTENDED | REG_NOSUB);
+
+ if (rc != 0) { /* Something is bad.. */
+ *errbuf = 0;
+ regerror(rc, &re, errbuf, sizeof(errbuf));
+ printf("%s:%d ERROR: Bad POSIX RE input, error: %s\n",
+ cf->name, cf->linenum, errbuf);
+ return 1;
+ }
+
+ /* param1 and str were processed successfully ... */
+
+ rep = calloc(1,sizeof(*rep));
+ *rep = re;
+
+ switch (groupcode) {
+ case 0:
+ src->sourceregscount += 1;
+ src->sourceregs =
+ realloc(src->sourceregs,
+ src->sourceregscount * sizeof(void *));
+ src->sourceregs[src->sourceregscount - 1] = rep;
+ break;
+ case 1:
+ src->destinationregscount += 1;
+ src->destinationregs =
+ realloc(src->destinationregs,
+ src->destinationregscount * sizeof(void *));
+ src->destinationregs[src->destinationregscount - 1] = rep;
+ break;
+ case 2:
+ src->viaregscount += 1;
+ src->viaregs = realloc(src->viaregs,
+ src->viaregscount * sizeof(void *));
+ src->viaregs[src->viaregscount - 1] = rep;
+ break;
+ case 3:
+ src->dataregscount += 1;
+ src->dataregs =
+ realloc(src->dataregs,
+ src->dataregscount * sizeof(void *));
+ src->dataregs[src->dataregscount - 1] = rep;
+ break;
+ }
+ return 0; // OK state
+}
+
+
+static int match_tracewide(const char *via, const struct tracewide *twp)
+{
+ int i;
+ if (twp == NULL) return 0;
+
+ for (i = 0; i < twp->nkeys; ++i) {
+ // if (debug>2) printf(" match:'%s'",twp->keys[i]);
+ if (memcmp(via, twp->keys[i], twp->keylens[i]) == 0) {
+ return twp->keylens[i];
+ }
+ }
+ return 0;
+}
+
+static int match_aliases(const char *via, struct aprx_interface *txif)
+{
+ int i;
+ for (i = 0; i < txif->aliascount; ++i) {
+ if (strcmp(via, txif->aliases[i]) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+static int count_single_tnc2_tracewide(struct viastate *state, const char *viafield, const int istrace, const int matchlen, const int viaindex)
+{
+ const char *p = viafield + matchlen;
+ const char reqc = p[0];
+ const char c = p[1];
+ const char remc = p[2];
+ int req, done;
+
+ int hasHflag = (strchr(viafield,'*') != NULL);
+
+ // Non-matched case, may have H-bit flag
+ if (matchlen == 0) {
+ /*
+ state->hopsreq += 0;
+ state->hopsdone += 0;
+ state->tracereq += 0;
+ state->tracedone += 0;
+ */
+ state->digireq += 1;
+ state->digidone += hasHflag;
+ if (viaindex == 2 && !hasHflag)
+ state->probably_heard_direct = 1;
+ // if (debug>1) printf(" a[req=%d,done=%d,trace=%d]",0,0,hasHflag);
+ return 0;
+ }
+
+ // Is the character following matched part one of: [1-7]
+ if (!('1' <= reqc && reqc <= '7')) {
+ // Not a digit, this is single matcher..
+ state->hopsreq += 1;
+ state->hopsdone += hasHflag;
+ if (istrace) {
+ state->tracereq += 1;
+ state->tracedone += hasHflag;
+ }
+ if (viaindex == 2 && !hasHflag)
+ state->probably_heard_direct = 1;
+ // if (debug>1) printf(" d[req=%d,done=%d]",1,hasHflag);
+ return 0;
+ }
+
+ req = reqc - '0';
+
+ if (c == '*' && remc == 0) { // WIDE1*
+ state->hopsreq += req;
+ state->hopsdone += req;
+ if (istrace) {
+ state->tracereq += req;
+ state->tracedone += req;
+ }
+ // if (debug>1) printf(" e[req=%d,done=%d]",req,req);
+ return 0;
+ }
+ if (c == 0) { // Bogus WIDE1 - uidigi puts these out.
+ state->fixthis = 1;
+ state->hopsreq += req;
+ state->hopsdone += req;
+ if (istrace) {
+ state->tracereq += req;
+ state->tracedone += req;
+ }
+ // if (debug>1) printf(" E[req=%d,done=%d]",req,req);
+ return 0;
+ }
+ // Not WIDE1-
+ if (c != '-') {
+ state->hopsreq += 1;
+ state->hopsdone += hasHflag;
+ if (istrace) {
+ state->tracereq += 1;
+ state->tracedone += hasHflag;
+ }
+ // if (debug>1) printf(" f[req=%d,done=%d]",1,hasHflag);
+ return 0;
+ }
+
+ // OK, it is "WIDEn-" plus "N"
+ if ('0' <= remc && remc <= '7' && p[3] == 0) {
+ state->hopsreq += req;
+ done = req - (remc - '0');
+ state->hopsdone += done;
+ if (done < 0) {
+ // Something like "WIDE3-7", which is definitely bogus!
+ state->fixall = 1;
+ if (viaindex == 2 && !hasHflag)
+ state->probably_heard_direct = 1;
+ return 0;
+ }
+ if (istrace) {
+ state->tracereq += req;
+ state->tracedone += done;
+ }
+ if (viaindex == 2) {
+ if (memcmp("TRACE",viafield,5)==0) // A real "TRACE" in first slot?
+ state->probably_heard_direct = 1;
+
+ else if (!hasHflag && done == 0) // WIDE3-3 on first slot
+ state->probably_heard_direct = 1;
+ }
+ // if (debug>1) printf(" g[req=%d,done=%d%s]",req,done,hasHflag ? ",Hflag!":"");
+ return 0;
+
+ } else if (('8' <= remc && remc <= '9' && p[3] == 0) ||
+ (remc == '1' && '0' <= p[3] && p[3] <= '5' && p[4] == 0)) {
+ // The request has SSID value in range of 8 to 15
+ state->fixall = 1;
+ if (viaindex == 2 && !hasHflag)
+ state->probably_heard_direct = 1;
+ return 0;
+
+ } else {
+ // Yuck, impossible/syntactically invalid
+ state->hopsreq += 1;
+ state->hopsdone += hasHflag;
+ if (istrace) {
+ state->tracereq += 1;
+ state->tracedone += hasHflag;
+ }
+ if (viaindex == 2 && !hasHflag)
+ state->probably_heard_direct = 1;
+ // if (debug>1) printf(" h[req=%d,done=%d]",1,hasHflag);
+ return 1;
+ }
+}
+
+static int match_transmitter(const char *viafield, const struct digipeater_source *src, const int lastviachar)
+{
+ struct aprx_interface *aif = src->parent->transmitter;
+ int tlen = strlen(aif->callsign);
+
+ if (memcmp(viafield, aif->callsign, tlen) == 0) {
+ if (viafield[tlen] == lastviachar)
+ return 1;
+ }
+
+ return 0;
+}
+
+static int try_reject_filters(const int fieldtype,
+ const char *field,
+ struct digipeater_source *src)
+{
+ int i;
+ int stat = 0;
+ switch (fieldtype) {
+ case 0: // Source
+ for (i = 0; i < src->sourceregscount; ++i) {
+ stat = regexec(src->sourceregs[i],
+ field, 0, NULL, 0);
+ if (stat == 0)
+ return 1; /* MATCH! */
+ }
+ if (memcmp("MYCALL",field,6)==0) return 1;
+ if (memcmp("N0CALL",field,6)==0) return 1;
+ if (memcmp("NOCALL",field,6)==0) return 1;
+ break;
+ case 1: // Destination
+
+ for (i = 0; i < src->destinationregscount; ++i) {
+ int stat = regexec(src->destinationregs[i],
+ field, 0, NULL, 0);
+ if (stat == 0)
+ return 1; /* MATCH! */
+ }
+ if (memcmp("MYCALL",field,6)==0) return 1;
+ if (memcmp("N0CALL",field,6)==0) return 1;
+ if (memcmp("NOCALL",field,6)==0) return 1;
+ break;
+ case 2: // Via
+
+ for (i = 0; i < src->viaregscount; ++i) {
+ int stat = regexec(src->viaregs[i],
+ field, 0, NULL, 0);
+ if (stat == 0)
+ return 1; /* MATCH! */
+ }
+ if (memcmp("MYCALL",field,6)==0) return 1;
+ if (memcmp("N0CALL",field,6)==0) return 1;
+ if (memcmp("NOCALL",field,6)==0) return 1;
+ break;
+ case 3: // Data
+
+ for (i = 0; i < src->dataregscount; ++i) {
+ int stat = regexec(src->dataregs[i],
+ field, 0, NULL, 0);
+ if (stat == 0)
+ return 1; /* MATCH! */
+ }
+ break;
+ default:
+ if (debug)
+ printf("try_reject_filters(fieldtype=%d) - CODE BUG\n",
+ fieldtype);
+ return 1;
+ }
+ if (stat != 0 && stat != REG_NOMATCH) {
+ // Some odd reason for an error?
+
+ }
+ return 0;
+}
+
+/* Parse executed and requested WIDEn-N/TRACEn-N info */
+static int parse_tnc2_hops(struct digistate *state, struct digipeater_source *src, struct pbuf_t *pb)
+{
+ const char *p = pb->dstcall_end+1;
+ const char *s;
+ const struct digipeater *digi = src->parent;
+ const char *lastviastar;
+ char viafield[15]; // temp buffer for many uses
+ int have_fault = 0;
+ int viaindex = 1; // First via index will be 2..
+ int activeviacount = 0;
+ int len;
+ int digiok;
+
+ if (debug>1) printf(" hops count of buffer: %s\n",p);
+
+ if (src->src_relaytype == DIGIRELAY_THIRDPARTY) {
+ state->v.hopsreq = 1; // Bonus for tx-igated 3rd-party frames
+ state->v.tracereq = 1; // Bonus for tx-igated 3rd-party frames
+ state->v.hopsdone = 0;
+ state->v.tracedone = 0;
+ state->v.probably_heard_direct = 1;
+ return 0;
+ }
+
+ // Copy the SRCCALL part of SRCALL>DSTCALL to viafield[] buffer
+ len = pb->srccall_end - pb->data;
+ if (len >= sizeof(viafield)) len = sizeof(viafield)-1;
+ memcpy(viafield, pb->data, len);
+ viafield[len] = 0;
+ // if (debug>2)printf(" srccall='%s'",viafield);
+ if (try_reject_filters(0, viafield, src)) {
+ if (debug>1) printf(" - Src filters reject\n");
+ return 1; // Src reject filters
+ }
+
+ // Copy the DSTCALL part of SRCALL>DSTCALL to viafield[] buffer
+ len = pb->dstcall_end - pb->srccall_end -1;
+ if (len >= sizeof(viafield)) len = sizeof(viafield)-1;
+ memcpy(viafield, pb->srccall_end+1, len);
+ viafield[len] = 0;
+ // if (debug>2)printf(" destcall='%s'",viafield);
+ if (try_reject_filters(1, viafield, src)) {
+ if (debug>1) printf(" - Dest filters reject\n");
+ return 1; // Dest reject filters
+ }
+
+ // Where is the last via-field with a start on it?
+ len = pb->info_start - p; if (len < 0) len=0;
+ lastviastar = memrchr(p, len, '*');
+
+ // Loop over VIA fields to see if we need to digipeat anything.
+ while (p < pb->info_start && !have_fault) {
+ len = 0;
+
+ if (*p == ':') {
+ // A round may stop at ':' found at the end of the processed field,
+ // then next round finds it at the start of the field.
+ break;
+ }
+
+ // Scan for a VIA field ...
+ for (s = p; s < pb->info_start; ++s) {
+ if (*s == ',' || *s == ':') {
+ // ... until comma or double-colon.
+ break;
+ }
+ }
+ // [p..s) is now one VIA field.
+ if (s == p && *p != ':') { // BAD!
+ have_fault = 1;
+ if (debug>1) printf(" S==P ");
+ break;
+ }
+ if (*p == 'q') break; // APRSIS q-constructs..
+ ++viaindex;
+
+ // Pick-up a viafield to separate buffer for processing
+ len = s-p;
+ if (len >= sizeof(viafield)) len = sizeof(viafield)-2;
+ memcpy(viafield, p, len);
+ viafield[len] = 0;
+ if (*s == ',') ++s;
+ p = s;
+
+ // Only last via field with H-bit is indicated at TNC2 format,
+ // but this digi code logic needs it at every VIA field where
+ // it is set. Therefore this crooked way to add it to picked
+ // up fields.
+ if (strchr(viafield,'*') == NULL) {
+ // If it exists somewhere, and we are not yet at it..
+ if (lastviastar != NULL && p < lastviastar)
+ strcat(viafield,"*"); // we do know that there is space for this.
+ }
+
+ if (debug>1) printf(" - ViaField[%d]: '%s'\n", viaindex, viafield);
+
+ // VIA-field picked up, now analyze it..
+
+ if (try_reject_filters(2, viafield, src)) {
+ if (debug>1) printf(" - Via filters reject\n");
+ return 1; // via reject filters
+ }
+
+ // Transmitter callsign match with H-flag set.
+ if (match_transmitter(viafield, src, '*')) {
+ if (debug>1) printf(" - Tx match reject\n");
+ // Oops, LOOP! I have transmit this in past
+ // (according to my transmitter callsign present
+ // in a VIA field!)
+ return 1;
+ }
+
+ // If there is no '*' meaning this has not been
+ // processed, then this is active field..
+ if (strchr(viafield, '*') == NULL)
+ ++activeviacount;
+
+ digiok = 0;
+
+ // If first active field (without '*') matches
+ // transmitter or alias, then this digi is accepted
+ // regardless if it is APRS or some other protocol.
+ if (activeviacount == 1 &&
+ (match_transmitter(viafield, src, 0) ||
+ match_aliases(viafield, digi->transmitter))) {
+ if (debug>1) printf(" - Tx match accept!\n");
+ state->v.hopsreq += 1;
+ state->v.tracereq += 1;
+ digiok = 1;
+ }
+
+ // .. otherwise following rules are applied only to APRS packets.
+ if (pb->is_aprs) {
+ if ((len = match_tracewide(viafield, src->src_trace))) {
+ have_fault = count_single_tnc2_tracewide(&state->v, viafield, 1, len, viaindex);
+ if (!have_fault)
+ digiok = 1;
+ } else if ((len = match_tracewide(viafield, digi->trace))) {
+ have_fault = count_single_tnc2_tracewide(&state->v, viafield, 1, len, viaindex);
+ if (!have_fault)
+ digiok = 1;
+ } else if ((len = match_tracewide(viafield, src->src_wide))) {
+ have_fault = count_single_tnc2_tracewide(&state->v, viafield, 0, len, viaindex);
+ if (!have_fault)
+ digiok = 1;
+ } else if ((len = match_tracewide(viafield, digi->wide))) {
+ have_fault = count_single_tnc2_tracewide(&state->v, viafield, 0, len, viaindex);
+ if (!have_fault)
+ digiok = 1;
+ } else {
+ // No match on trace or wide, but if there was earlier
+ // match on interface or alias, then it set "digiok" for us.
+ }
+ }
+ if (state->v.fixthis || state->v.fixall) {
+ // Argh.. bogus WIDEn seen, which is what UIDIGIs put out..
+ // Also some other broken requests are "fixed": like WIDE3-7
+ // Fixing it: We set the missing H-bit, and continue processing.
+ // (That fixing is done in incoming AX25 address field, which
+ // we generally do not touch - with this exception.)
+ pb->ax25addr[ AX25ADDRLEN*viaindex + AX25ADDRLEN-1 ] |= AX25HBIT;
+ state->v.fixthis = 0;
+ }
+ if (!digiok) {
+ if (debug>1) printf(" this via field is not matching with a TRACE, WIDE, ALIAS or INTERFACE.\n");
+ break;
+ }
+ }
+ if (debug>1) printf(" req=%d,done=%d [%s,%s,%s]\n",
+ state->v.hopsreq,state->v.hopsdone,
+ have_fault ? "FAULT":"OK",
+ (state->v.hopsreq > state->v.hopsdone) ? "DIGIPEAT":"DROP",
+ (state->v.tracereq > state->v.tracedone) ? "TRACE":"WIDE");
+ return have_fault;
+}
+
+
+static void free_tracewide(struct tracewide *twp)
+{
+ int i;
+
+ if (twp == NULL) return;
+ if (twp->keys) {
+ for (i = 0; i < twp->nkeys; ++i)
+ free((void*)(twp->keys[i]));
+ free(twp->keys);
+ }
+ if (twp->keylens)
+ free((void*)(twp->keylens));
+
+ free(twp);
+}
+static void free_source(struct digipeater_source *src)
+{
+ if (src == NULL) return;
+ free(src);
+}
+
+static struct tracewide *digipeater_config_tracewide(struct configfile *cf, int is_trace)
+{
+ char *name, *param1;
+ char *str = cf->buf;
+ int has_fault = 0;
+ int nkeys = 0;
+ char **keywords = NULL;
+ int *keylens = NULL;
+ int maxreq = 4;
+ int maxdone = 4;
+ struct tracewide *tw;
+
+ while (readconfigline(cf) != NULL) {
+ if (configline_is_comment(cf))
+ continue; /* Comment line, or empty line */
+
+ // It can be severely indented...
+ str = config_SKIPSPACE(cf->buf);
+
+ name = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+ config_STRLOWER(name);
+
+ param1 = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ if (is_trace) {
+ if (strcmp(name, "</trace>") == 0) {
+ break;
+ }
+ } else {
+ if (strcmp(name, "</wide>") == 0) {
+ break;
+ }
+ }
+
+ // ... actual parameters
+ if (strcmp(name,"maxreq") == 0) {
+ maxreq = atoi(param1);
+ // if (debug) printf(" maxreq %d\n",maxreq);
+
+ } else if (strcmp(name,"maxdone") == 0) {
+ maxdone = atoi(param1);
+ // if (debug) printf(" maxdone %d\n",maxdone);
+
+ } else if (strcmp(name,"keys") == 0) {
+ char *k = strtok(param1, ",");
+ for (; k ; k = strtok(NULL,",")) {
+ ++nkeys;
+ // if (debug) printf(" n=%d key='%s'\n",nkeys,k);
+ keywords = realloc(keywords, sizeof(char*) * nkeys);
+ keywords[nkeys-1] = strdup(k);
+
+ keylens = realloc(keylens, sizeof(int) * nkeys);
+ keylens[nkeys-1] = strlen(k);
+ }
+
+ } else {
+ has_fault = 1;
+ printf("%s:%d ERROR: Unknown keyword inside %s subblock: '%s'\n",
+ cf->name, cf->linenum, is_trace ? "<trace>":"<wide>", name);
+ }
+ }
+
+ if (has_fault) {
+ int i;
+ for (i = 0; i < nkeys; ++i)
+ free(keywords[i]);
+ if (keywords != NULL)
+ free(keywords);
+ if (keylens != NULL)
+ free(keylens);
+ return NULL;
+ }
+
+ tw = calloc(1,sizeof(*tw));
+
+ tw->maxreq = maxreq;
+ tw->maxdone = maxdone;
+ tw->is_trace = is_trace;
+ tw->nkeys = nkeys;
+ tw->keys = keywords;
+ tw->keylens = keylens;
+
+ return tw;
+}
+
+static struct digipeater_source *digipeater_config_source(struct configfile *cf)
+{
+ char *name, *param1;
+ char *str = cf->buf;
+ int has_fault = 0;
+ int viscous_delay = 0;
+ float ratelimit = 120;
+ float rateincrement = 60;
+
+ struct aprx_interface *source_aif = NULL;
+ struct digipeater_source *source = NULL;
+ digi_relaytype relaytype = DIGIRELAY_DIGIPEAT;
+ struct filter_t *filters = NULL;
+ struct tracewide *source_trace = NULL;
+ struct tracewide *source_wide = NULL;
+ struct digipeater_source regexsrc;
+#ifndef DISABLE_IGATE
+ char *via_path = NULL;
+ char *msg_path = NULL;
+ uint8_t ax25viapath[AX25ADDRLEN];
+ uint8_t msgviapath[AX25ADDRLEN];
+#endif
+
+ memset(®exsrc, 0, sizeof(regexsrc));
+#ifndef DISABLE_IGATE
+ memset(ax25viapath, 0, sizeof(ax25viapath));
+ memset(msgviapath, 0, sizeof(msgviapath));
+#endif
+
+ while (readconfigline(cf) != NULL) {
+ if (configline_is_comment(cf))
+ continue; /* Comment line, or empty line */
+
+ // It can be severely indented...
+ str = config_SKIPSPACE(cf->buf);
+
+ name = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+ config_STRLOWER(name);
+
+ param1 = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ if (strcmp(name, "</source>") == 0) {
+ break;
+
+ // ... actual parameters
+ } else if (strcmp(name,"source") == 0) {
+ if (debug)
+ printf("%s:%d <source> source = '%s'\n",
+ cf->name, cf->linenum, param1);
+
+ if (strcasecmp(param1,"$mycall") == 0)
+ param1 = (char*)mycall;
+
+ source_aif = find_interface_by_callsign(param1);
+ if (source_aif == NULL) {
+ has_fault = 1;
+ printf("%s:%d ERROR: Digipeater source '%s' not found\n",
+ cf->name, cf->linenum, param1);
+ }
+ if (debug>1)
+ printf(" .. source_aif = %p\n", source_aif);
+
+ } else if (strcmp(name, "viscous-delay") == 0) {
+ viscous_delay = atoi(param1);
+ if (debug) printf(" viscous-delay = %d\n",viscous_delay);
+ if (viscous_delay < 0) {
+ printf("%s:%d ERROR: Bad value for viscous-delay: '%s'\n",
+ cf->name, cf->linenum, param1);
+ viscous_delay = 0;
+ has_fault = 1;
+ }
+ if (viscous_delay > 9) {
+ printf("%s:%d ERROR: Too large value for viscous-delay: '%s'\n",
+ cf->name, cf->linenum, param1);
+ viscous_delay = 9;
+ has_fault = 1;
+ }
+
+ } else if (strcmp(name, "ratelimit") == 0) {
+ char *param2 = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ rateincrement = (float)atof(param1);
+ ratelimit = (float)atof(param2);
+ if (rateincrement < 0.01 || rateincrement > rateincrementmax)
+ rateincrement = 60;
+ if (ratelimit < 0.01 || ratelimit > ratelimitmax)
+ ratelimit = 120;
+ if (ratelimit < rateincrement)
+ rateincrement = ratelimit;
+ if (debug)
+ printf(" .. ratelimit %f %f\n",
+ rateincrement, ratelimit);
+
+ } else if (strcmp(name,"regex-filter") == 0) {
+ if (regex_filter_add(cf, ®exsrc, param1, str)) {
+ // prints errors internally
+ has_fault = 1;
+ }
+
+#ifndef DISABLE_IGATE
+ } else if (strcmp(name, "via-path") == 0) {
+
+ // Validate that source callsign is "APRSIS"
+ // or "DPRS" for this parameter
+
+ if (source_aif == NULL ||
+ (strcmp(source_aif->callsign,"APRSIS") != 0 &&
+ strcmp(source_aif->callsign,"DPRS") != 0)) {
+ printf("%s:%d ERROR: via-path parameter is available only on 'source APRSIS' and 'source DPRS' cases.\n",
+ cf->name, cf->linenum);
+ has_fault = 1;
+ continue;
+ }
+
+ via_path = strdup(param1);
+ config_STRUPPER(via_path);
+
+ if (parse_ax25addr(ax25viapath, via_path, 0x00)) {
+ has_fault = 1;
+ printf("%s:%d ERROR: via-path parameter is not valid AX.25 callsign: '%s'\n",
+ cf->name, cf->linenum, via_path);
+ free(via_path);
+ via_path = NULL;
+ continue;
+ }
+
+ if (debug)
+ printf("via-path '%s'\n", via_path);
+
+ } else if (strcmp(name, "msg-path") == 0) {
+
+ // Validate that source callsign is "APRSIS"
+ // or "DPRS" for this parameter
+
+ if (source_aif == NULL ||
+ (strcmp(source_aif->callsign,"APRSIS") != 0 &&
+ strcmp(source_aif->callsign,"DPRS") != 0)) {
+ printf("%s:%d ERROR: msg-path parameter is available only on 'source APRSIS' and 'source DPRS' cases.\n",
+ cf->name, cf->linenum);
+ has_fault = 1;
+ continue;
+ }
+
+ msg_path = strdup(param1);
+ config_STRUPPER(msg_path);
+
+ if (parse_ax25addr(msgviapath, msg_path, 0x00)) {
+ has_fault = 1;
+ printf("%s:%d ERROR: msg-path parameter is not valid AX.25 callsign: '%s'\n",
+ cf->name, cf->linenum, msg_path);
+ free(msg_path);
+ msg_path = NULL;
+ continue;
+ }
+
+ if (debug)
+ printf("msg-path '%s'\n", msg_path);
+#endif
+ } else if (strcmp(name,"<trace>") == 0) {
+ if (source_trace == NULL) {
+ source_trace = digipeater_config_tracewide(cf, 1);
+ // prints errors internally
+ } else {
+ has_fault = 1;
+ printf("%s:%d ERROR: double definition of <trace> block.\n",
+ cf->name, cf->linenum);
+ }
+
+ } else if (strcmp(name,"<wide>") == 0) {
+ if (source_wide == NULL) {
+ source_wide = digipeater_config_tracewide(cf, 0);
+ // prints errors internally
+ } else {
+ has_fault = 1;
+ printf("%s:%d ERROR: double definition of <wide> block.\n",
+ cf->name, cf->linenum);
+ }
+
+ } else if (strcmp(name,"filter") == 0) {
+ if (filter_parse(&filters, param1)) {
+ // prints errors internally
+
+ has_fault = 1;
+ printf("%s:%d ERROR: Error at filter parser.\n",
+ cf->name, cf->linenum);
+ } else {
+ if (debug)
+ printf(" .. OK filter %s\n", param1);
+ }
+
+ } else if (strcmp(name,"relay-type") == 0 || // documented name
+ strcmp(name,"relay-format") == 0 || // an alias
+ strcmp(name,"digi-mode") == 0) { // very old alias
+ config_STRLOWER(param1);
+ if (strcmp(param1,"digipeat") == 0) {
+ relaytype = DIGIRELAY_DIGIPEAT;
+ } else if (strcmp(param1,"digipeated") == 0) {
+ relaytype = DIGIRELAY_DIGIPEAT;
+ } else if (strcmp(param1,"digipeater") == 0) {
+ relaytype = DIGIRELAY_DIGIPEAT;
+ } else if (strcmp(param1,"directonly") == 0) {
+ relaytype = DIGIRELAY_DIGIPEAT_DIRECTONLY;
+ } else if (strcmp(param1,"third-party") == 0) {
+ relaytype = DIGIRELAY_THIRDPARTY;
+ } else if (strcmp(param1,"3rd-party") == 0) {
+ relaytype = DIGIRELAY_THIRDPARTY;
+ } else {
+ printf("%s:%d ERROR: Digipeater <source>'s %s did not recognize: '%s' \n", cf->name, cf->linenum, name, param1);
+ has_fault = 1;
+ }
+ } else {
+ printf("%s:%d ERROR: Digipeater <source>'s %s did not recognize: '%s' \n", cf->name, cf->linenum, name, param1);
+ has_fault = 1;
+ }
+ }
+
+ if (source_aif == NULL) {
+ has_fault = 1;
+ printf("%s:%d ERROR: Missing or bad 'source =' definition at this <source> group.\n",
+ cf->name, cf->linenum);
+ }
+
+ if (!has_fault && (source_aif != NULL)) {
+ source = calloc(1,sizeof(*source));
+
+ source->src_if = source_aif;
+ source->src_relaytype = relaytype;
+ source->src_filters = filters;
+ source->src_trace = source_trace;
+ source->src_wide = source_wide;
+#ifndef DISABLE_IGATE
+ source->via_path = via_path;
+ source->msg_path = msg_path;
+ memcpy(source->ax25viapath, ax25viapath, sizeof(ax25viapath));
+ memcpy(source->msgviapath, msgviapath, sizeof(msgviapath));
+ if (msg_path == NULL) { // default value of via-path !
+ source->msg_path = via_path;
+ memcpy(source->msgviapath, ax25viapath, sizeof(ax25viapath));
+ }
+#endif
+
+ source->viscous_delay = viscous_delay;
+
+ source->tbf_limit = (ratelimit * TOKENBUCKET_INTERVAL)/60;
+ source->tbf_increment = (rateincrement * TOKENBUCKET_INTERVAL)/60;
+ source->tokenbucket = source->tbf_limit;
+
+ // RE pattern reject filters
+ source->sourceregscount = regexsrc.sourceregscount;
+ source->sourceregs = regexsrc.sourceregs;
+ source->destinationregscount = regexsrc.destinationregscount;
+ source->destinationregs = regexsrc.destinationregs;
+ source->viaregscount = regexsrc.viaregscount;
+ source->viaregs = regexsrc.viaregs;
+ source->dataregscount = regexsrc.dataregscount;
+ source->dataregs = regexsrc.dataregs;
+
+ } else {
+ // Errors detected
+ free_tracewide(source_trace);
+ free_tracewide(source_wide);
+ // filters_free(filters);
+ // free regexsrc's allocations
+ if (debug)
+ printf("Seen errors at <digipeater><source> definition.\n");
+ }
+
+ if (debug>1)printf(" .. <source> definition returning %p\n",source);
+ return source;
+}
+
+int digipeater_config(struct configfile *cf)
+{
+ char *name, *param1;
+ char *str = cf->buf;
+ int has_fault = 0;
+ int i;
+ const int line0 = cf->linenum;
+
+ struct aprx_interface *aif = NULL;
+ float ratelimit = 60;
+ float rateincrement = 60;
+ float srcratelimit = 60;
+ float srcrateincrement = 60;
+ int sourcecount = 0;
+ int dupestoretime = 30; // FIXME: parametrize! 30 is minimum..
+ struct digipeater_source **sources = NULL;
+ struct digipeater *digi = NULL;
+ struct tracewide *traceparam = NULL;
+ struct tracewide *wideparam = NULL;
+
+ while (readconfigline(cf) != NULL) {
+ if (configline_is_comment(cf))
+ continue; /* Comment line, or empty line */
+
+ // It can be severely indented...
+ str = config_SKIPSPACE(cf->buf);
+
+ name = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+ config_STRLOWER(name);
+
+ param1 = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ if (strcmp(name, "</digipeater>") == 0) {
+ break;
+ }
+ if (strcmp(name, "transmit") == 0 ||
+ strcmp(name, "transmitter") == 0) {
+ if (strcasecmp(param1,"$mycall") == 0)
+ param1 = (char*)mycall;
+
+ aif = find_interface_by_callsign(param1);
+ if (aif != NULL && (!aif->tx_ok)) {
+ aif = NULL; // Not
+ printf("%s:%d ERROR: This transmit interface has no TX-OK TRUE setting: '%s'\n",
+ cf->name, cf->linenum, param1);
+ has_fault = 1;
+ } else if (aif != NULL && aif->txrefcount > 0) {
+ aif = NULL;
+ printf("%s:%d ERROR: This transmit interface is being used on multiple <digipeater>s as transmitter: '%s'\n",
+ cf->name, cf->linenum, param1);
+ has_fault = 1;
+ } else if (aif == NULL) {
+ printf("%s:%d ERROR: Unknown interface: '%s'\n",
+ cf->name, cf->linenum, param1);
+ has_fault = 1;
+ }
+
+ } else if (strcmp(name, "ratelimit") == 0) {
+ char *param2 = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ rateincrement = (float)atof(param1);
+ ratelimit = (float)atof(param2);
+ if (rateincrement < 0.01 || rateincrement > rateincrementmax)
+ rateincrement = 60;
+ if (ratelimit < 0.01 || ratelimit > ratelimitmax)
+ ratelimit = 60;
+ if (ratelimit < rateincrement)
+ rateincrement = ratelimit;
+ if (debug)
+ printf(" .. ratelimit %f %f\n",
+ rateincrement, ratelimit);
+
+ } else if (strcmp(name, "srcratelimit") == 0) {
+ char *param2 = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ srcrateincrement = (float)atof(param1);
+ srcratelimit = (float)atof(param2);
+ if (srcrateincrement < 0.01 || srcrateincrement > rateincrementmax)
+ srcrateincrement = 60;
+ if (srcratelimit < 0.01 || srcratelimit > ratelimitmax)
+ srcratelimit = 60;
+ if (srcratelimit < srcrateincrement)
+ srcrateincrement = srcratelimit;
+ if (debug)
+ printf(" .. srcratelimit %f %f\n",
+ srcrateincrement, srcratelimit);
+
+ } else if (strcmp(name, "<trace>") == 0) {
+ if (traceparam == NULL) {
+ traceparam = digipeater_config_tracewide(cf, 1);
+ if (traceparam == NULL) {
+ printf("%s:%d ERROR: <trace> definition failed!\n",
+ cf->name, cf->linenum);
+ has_fault = 1;
+ }
+ } else {
+ printf("%s:%d ERROR: Double definition of <trace> !\n",
+ cf->name, cf->linenum);
+ has_fault = 1;
+ }
+
+ } else if (strcmp(name, "<wide>") == 0) {
+ if (wideparam == NULL) {
+ wideparam = digipeater_config_tracewide(cf, 0);
+ if (wideparam == NULL) {
+ printf("%s:%d ERROR: <wide> definition failed!\n",
+ cf->name, cf->linenum);
+ has_fault = 1;
+ }
+ } else {
+ printf("%s:%d ERROR: Double definition of <wide> !\n",
+ cf->name, cf->linenum);
+ has_fault = 1;
+ }
+
+ } else if (strcmp(name, "<source>") == 0) {
+ struct digipeater_source *src =
+ digipeater_config_source(cf);
+ if (src != NULL) {
+ // Found a source, link it!
+ sources = realloc(sources, sizeof(void*) * (sourcecount+1));
+ sources[sourcecount] = src;
+ ++sourcecount;
+ } else {
+ has_fault = 1;
+ printf("%s:%d ERROR: <source> definition failed\n",
+ cf->name, cf->linenum);
+ }
+
+ } else {
+ printf("%s:%d ERROR: Unknown <digipeater> config keyword: '%s'\n",
+ cf->name, cf->linenum, name);
+ has_fault = 1;
+ continue;
+ }
+ }
+
+ if (aif == NULL && !has_fault) {
+ printf("%s:%d ERROR: Digipeater defined without transmit interface.\n",
+ cf->name, cf->linenum);
+ has_fault = 1;
+ }
+ if (sourcecount == 0 && !has_fault) {
+ printf("%s:%d ERROR: Digipeater defined without <source>:s.\n",
+ cf->name, cf->linenum);
+ has_fault = 1;
+ }
+ // Check that source definitions are unique
+ for ( i = 0; i < sourcecount; ++i ) {
+ int j;
+ for (j = i+1; j < sourcecount; ++j) {
+ if (sources[i]->src_if == sources[j]->src_if) {
+ has_fault = 1;
+ printf("%s:%d Two <source>s on this <digipeater> definition use same <interface>: '%s'\n",
+ cf->name, line0, sources[i]->src_if->callsign);
+ }
+ }
+ }
+
+ if (has_fault) {
+ // Free allocated resources and link pointers, if any
+ for ( i = 0; i < sourcecount; ++i ) {
+ free_source(sources[i]);
+ }
+ if (sources != NULL)
+ free(sources);
+
+ free_tracewide(traceparam);
+ free_tracewide(wideparam);
+
+ printf("ERROR: Config fault observed on <digipeater> definitions! \n");
+ } else {
+ // Construct the digipeater
+
+ digi = calloc(1,sizeof(*digi));
+
+ if (debug>1)printf("<digipeater> sourcecount=%d\n",sourcecount);
+
+ // up-link all interfaces used as sources
+ for ( i = 0; i < sourcecount; ++i ) {
+ struct digipeater_source *src = sources[i];
+ src->parent = digi; // Set parent link
+
+ src->src_if->digisources = realloc( src->src_if->digisources,
+ (src->src_if->digisourcecount +1) * (sizeof(void*)));
+ src->src_if->digisources[src->src_if->digisourcecount] = src;
+ src->src_if->digisourcecount += 1;
+ }
+
+ aif->txrefcount += 1; // Increment Tx usage Reference count.
+ // We permit only one <digipeater> to
+ // use any given Tx-interface. (Rx:es
+ // permit multiple uses.)
+ digi->transmitter = aif;
+ digi->tbf_limit = (ratelimit * TOKENBUCKET_INTERVAL)/60;
+ digi->tbf_increment = (rateincrement * TOKENBUCKET_INTERVAL)/60;
+ digi->src_tbf_limit = (srcratelimit * TOKENBUCKET_INTERVAL)/60;
+ digi->src_tbf_increment = (srcrateincrement * TOKENBUCKET_INTERVAL)/60;
+ digi->tokenbucket = digi->tbf_limit;
+
+ digi->dupechecker = dupecheck_new(dupestoretime); // Dupecheck is per transmitter
+#ifndef DISABLE_IGATE
+ digi->historydb = historydb_new(); // HistoryDB is per transmitter
+#endif
+
+ digi->trace = (traceparam != NULL) ? traceparam : & default_trace_param;
+ digi->wide = (wideparam != NULL) ? wideparam : & default_wide_param;
+
+ digi->sourcecount = sourcecount;
+ digi->sources = sources;
+
+ digis = realloc( digis, sizeof(void*) * (digi_count+1));
+ digis[digi_count] = digi;
+ ++digi_count;
+ }
+ return has_fault;
+}
+
+
+static int decrement_ssid(uint8_t *ax25addr)
+{
+ // bit-field manipulation
+ int ssid = (ax25addr[AX25ADDRLEN-1] >> 1) & 0x0F;
+ if (ssid > 0)
+ --ssid;
+ ax25addr[AX25ADDRLEN-1] = (ax25addr[AX25ADDRLEN-1] & 0xE1) | (ssid << 1);
+ return ssid;
+}
+
+
+/* 0 == accept, otherwise reject */
+/*
+int digipeater_receive_filter(struct digipeater_source *src, struct pbuf_t *pb)
+{
+
+ if (src->src_filters == NULL) {
+ if (debug>1)
+ printf("No source filters, accepted the packet from %s.\n", src->src_if->callsign);
+ return 0;
+ }
+ int rc = filter_process(pb, src->src_filters, src->parent->historydb);
+ if (rc != 1) {
+ if (debug>1)
+ printf("Source filtering rejected the packet from %s.\n", src->src_if->callsign);
+ return 1;
+ }
+ if (debug>1)
+ printf("Source filtering accepted the packet from %s.\n", src->src_if->callsign);
+ return 0;
+}
+*/
+
+static void digipeater_receive_backend(struct digipeater_source *src, struct pbuf_t *pb)
+{
+ int len, viaindex;
+ struct digistate state;
+ struct viastate viastate;
+ struct digipeater *digi = src->parent;
+ char viafield[14]; // room for text format
+ uint8_t *axaddr, *e;
+
+ memset(&state, 0, sizeof(state));
+ memset(&viastate, 0, sizeof(viastate));
+
+ // 2) Verify that none of our interface callsigns does match any
+ // of already DIGIPEATED via fields! (fields that have H-bit set)
+ // ( present implementation: this digi's transmitter callsign is
+ // verified)
+
+ // Parse executed and requested WIDEn-N/TRACEn-N info
+ if (parse_tnc2_hops(&state, src, pb)) {
+ // A fault was observed! -- tests include "not this transmitter"
+ if (debug>1)
+ printf("Parse_tnc2_hops rejected this.");
+ return;
+ }
+
+ if (pb->is_aprs) {
+
+ if (state.v.probably_heard_direct) {
+ // Collect a decaying average of distances to stations?
+ // .. could auto-beacon an aloha-circle - maybe
+ // .. note: this does not get packets that have no VIA fields.
+ // Score of direct DX:es?
+ // .. note: this does not get packets that have no VIA fields.
+ } else {
+ if (src->src_relaytype == DIGIRELAY_DIGIPEAT_DIRECTONLY) {
+ // Source relaytype is DIRECTONLY, and this was not
+ // likely directly heard...
+ if (debug>1) printf("DIRECTONLY -mode, and packet is probably not direct heard.");
+ return;
+ }
+ }
+ // Keep score of all DX packets?
+
+ if (try_reject_filters(3, pb->info_start, src)) {
+ if (debug>1)
+ printf(" - Data body regexp filters reject\n");
+ return; // data body regexp reject filters
+ }
+
+ // FIXME: 3) aprsc style filters checking in service area of the packet..
+
+ }
+
+ // 4) Hop-count filtering:
+
+ // APRSIS sourced packets have different rules than DIGIPEAT
+ // packets...
+ if (state.v.hopsreq <= state.v.hopsdone) {
+ if (debug>1) printf(" No remaining hops to execute.\n");
+ return;
+ }
+ if (state.v.hopsreq > digi->trace->maxreq ||
+ state.v.hopsreq > digi->wide->maxreq ||
+ state.v.tracereq > digi->trace->maxreq ||
+ state.v.hopsdone > digi->trace->maxdone ||
+ state.v.hopsdone > digi->wide->maxdone ||
+ state.v.tracedone > digi->trace->maxdone) {
+ if (debug) printf(" Packet exceeds digipeat limits\n");
+ if (!state.v.probably_heard_direct) {
+ if (debug) printf(".. discard.\n");
+ return;
+ } else {
+ state.v.fixall = 1;
+ }
+ }
+
+ // if (debug) printf(" Packet accepted to digipeat!\n");
+
+ state.ax25addrlen = pb->ax25addrlen;
+ memcpy(state.ax25addr, pb->ax25addr, pb->ax25addrlen);
+ axaddr = state.ax25addr + 2*AX25ADDRLEN;
+ e = state.ax25addr + state.ax25addrlen;
+
+ if (state.v.fixall) {
+ // Okay, insert my transmitter callsign on the first
+ // VIA field, and mark the rest with H-bit
+ // (in search loop below)
+ int taillen = e - axaddr;
+ if (state.ax25addrlen >= AX25ADDRMAXLEN) {
+ if (debug) printf(" FIXALL TRACE overgrows the VIA fields! Dropping last of incoming ones.\n");
+ // Drop the last via field to make room for insert below.
+ state.ax25addrlen -= AX25ADDRLEN;
+ taillen -= AX25ADDRLEN;
+ }
+ // If we have a tail, move it up (there is always room for it)
+ if (taillen > 0)
+ memmove(axaddr+AX25ADDRLEN, axaddr, taillen);
+ state.ax25addrlen += AX25ADDRLEN;
+ e = state.ax25addr + state.ax25addrlen; // recalculate!
+
+ // Put the transmitter callsign in
+ memcpy(axaddr, digi->transmitter->ax25call, AX25ADDRLEN);
+
+ // Set Address Termination bit at the last VIA field
+ // (possibly ours, or maybe the previous one was truncated..)
+ axaddr[state.ax25addrlen-1] |= AX25ATERM;
+ }
+
+ // Search for first AX.25 VIA field that does not have H-bit set:
+ viaindex = 1; // First via field is number 2
+ *viafield = 0; // clear that buffer for starters
+ for (; axaddr < e; axaddr += AX25ADDRLEN, ++viaindex) {
+ ax25_to_tnc2_fmtaddress(viafield, axaddr, 0);
+ // if (debug>1) {
+ // printf(" via: %s", viafield);
+ // }
+
+ // Initial parsing said that things are seriously wrong..
+ // .. and we will digipeat the packet with all H-bits set.
+ if (state.v.fixall) axaddr[AX25ADDRLEN-1] |= AX25HBIT;
+
+ if (!(axaddr[AX25ADDRLEN-1] & AX25HBIT)) // No "Has Been Digipeated" bit set
+ break; // this doesn't happen in "fixall" mode
+ }
+
+ switch (src->src_relaytype) {
+ case DIGIRELAY_THIRDPARTY:
+ // Effectively disable the digipeat modifying of address
+ axaddr = e;
+ break;
+ case DIGIRELAY_DIGIPEAT:
+ // Normal functionality
+ break;
+ default: ;
+ }
+
+ // Unprocessed VIA field found (not in FIXALL mode)
+ if (axaddr < e) { // VIA-field of interest has been found
+
+// FIXME: 5) / 6) Cross-frequency/cross-band digipeat may add a special
+// label telling that the message originated on other band
+
+ // 7) WIDEn-N treatment (as well as transmitter matching digi)
+ if (pb->digi_like_aprs) {
+ if (strcmp(viafield,digi->transmitter->callsign) == 0 ||
+ // Match on the transmitter callsign without the star...
+ match_aliases(viafield, digi->transmitter)) {
+ // .. or match transmitter interface alias.
+
+ // Treat it as a TRACE request.
+
+ int aterm = axaddr[AX25ADDRLEN-1] & AX25ATERM; // save old address termination bit
+ // Put the transmitter callsign in, and set the H-bit.
+ memcpy(axaddr, digi->transmitter->ax25call, AX25ADDRLEN);
+ axaddr[AX25ADDRLEN-1] |= (AX25HBIT | aterm); // Set H-bit
+
+ } else if ((len = match_tracewide(viafield, src->src_trace))) {
+ count_single_tnc2_tracewide(&viastate, viafield, 1, len, viaindex);
+ } else if ((len = match_tracewide(viafield, digi->trace))) {
+ count_single_tnc2_tracewide(&viastate, viafield, 1, len, viaindex);
+ } else if ((len = match_tracewide(viafield, src->src_wide))) {
+ count_single_tnc2_tracewide(&viastate, viafield, 0, len, viaindex);
+ } else if ((len = match_tracewide(viafield, digi->wide))) {
+ count_single_tnc2_tracewide(&viastate, viafield, 0, len, viaindex);
+ }
+
+ } else { // Not "digi_as_aprs" rules
+
+ if (strcmp(viafield,digi->transmitter->callsign) == 0) {
+ // Match on the transmitter callsign without the star.
+ // Treat it as a TRACE request.
+ int aterm = axaddr[AX25ADDRLEN-1] & AX25ATERM; // save old address termination bit
+ // Put the transmitter callsign in, and set the H-bit.
+ memcpy(axaddr, digi->transmitter->ax25call, AX25ADDRLEN);
+ axaddr[AX25ADDRLEN-1] |= (AX25HBIT | aterm); // Set H-bit
+
+ } else if (match_aliases(viafield, digi->transmitter)) {
+ // Match on the aliases.
+ // Treat it as a TRACE request.
+ int aterm = axaddr[AX25ADDRLEN-1] & AX25ATERM; // save old address termination bit
+ // Put the transmitter callsign in, and set the H-bit.
+ memcpy(axaddr, digi->transmitter->ax25call, AX25ADDRLEN);
+ axaddr[AX25ADDRLEN-1] |= (AX25HBIT | aterm); // Set H-bit
+ }
+ }
+
+ if (viastate.tracereq > viastate.tracedone) {
+ // if (debug) printf(" TRACE on %s!\n",viafield);
+ // Must move it up in memory to be able to put
+ // transmitter callsign in
+ int taillen = e - axaddr;
+ int newssid;
+ if (state.ax25addrlen >= AX25ADDRMAXLEN) {
+ if (debug) printf(" TRACE overgrows the VIA fields! Discard.\n");
+ return;
+ }
+ memmove(axaddr+AX25ADDRLEN, axaddr, taillen);
+ state.ax25addrlen += AX25ADDRLEN;
+
+ newssid = decrement_ssid(axaddr+AX25ADDRLEN);
+ if (newssid <= 0)
+ axaddr[2*AX25ADDRLEN-1] |= AX25HBIT; // Set H-bit
+ // Put the transmitter callsign in, and set the H-bit.
+ memcpy(axaddr, digi->transmitter->ax25call, AX25ADDRLEN);
+ axaddr[AX25ADDRLEN-1] |= AX25HBIT; // Set H-bit
+
+ } else if (viastate.hopsreq > viastate.hopsdone) {
+ // If configuration didn't process "WIDE" et.al. as
+ // a TRACE, then here we process them without trace..
+ int newssid;
+ if (debug) printf(" VIA on %s!\n",viafield);
+ newssid = decrement_ssid(axaddr);
+ if (newssid <= 0)
+ axaddr[AX25ADDRLEN-1] |= AX25HBIT; // Set H-bit
+ }
+ }
+ {
+ history_cell_t *hcell;
+ char tbuf[2800];
+ int is_ui = 0, ui_pid = -1, frameaddrlen = 0, tnc2addrlen = 0, t2l;
+ // uint8_t *u = state.ax25addr + state.ax25addrlen;
+ // *u++ = 0;
+ // *u++ = 0;
+ // *u++ = 0;
+ t2l = ax25_format_to_tnc( state.ax25addr,
+ state.ax25addrlen+AX25ADDRLEN-1,
+ tbuf, sizeof(tbuf),
+ & frameaddrlen, &tnc2addrlen,
+ & is_ui, &ui_pid );
+ tbuf[t2l] = 0;
+ if (debug) {
+ printf(" out-hdr: '%s' data='",tbuf);
+ (void)fwrite(pb->ax25data+2, pb->ax25datalen-2, // without Control+PID
+ 1, stdout);
+ printf("'\n");
+ }
+
+#ifndef DISABLE_IGATE
+ // Insert into history database - track every packet
+ hcell = historydb_insert_( digi->historydb, pb, 1 );
+
+ if (hcell != NULL) {
+ if (hcell->tokenbucket < 1.0) {
+ if (debug) printf("TRANSMITTER SOURCE CALLSIGN RATELIMIT DISCARD.\n");
+ return;
+ }
+ hcell->tokenbucket -= 1.0;
+ }
+#endif
+
+ // Now we do token bucket filtering -- rate limiting
+ if (digi->tokenbucket < 1.0) {
+ if (debug) printf("TRANSMITTER RATELIMIT DISCARD.\n");
+ return;
+ }
+ digi->tokenbucket -= 1.0;
+
+ if (pb->is_aprs && rflogfile) {
+ int t2l2;
+ // Essentially Debug logging.. to file
+
+ if (sizeof(tbuf) - pb->ax25datalen > t2l && t2l > 0) {
+ // Have space for body too, skip leading Ctrl+PID bytes
+ memcpy(tbuf+t2l, pb->ax25data+2, pb->ax25datalen-2); // Ctrl+PID skiped
+ t2l2 = t2l + pb->ax25datalen-2; // tbuf size sans Ctrl+PID
+
+ rflog( digi->transmitter->callsign, 'T', 0, tbuf, t2l2 );
+ tbuf[t2l]=0;
+ }
+ }
+
+ // Feed to dupe-filter (transmitter specific)
+ // this means we have already seen it, and when
+ // it comes back from somewhere, we do not digipeat
+ // it ourselves.
+
+ // This recording is needed at output side of digipeater
+ // for APRSIS and DPRS transmit gates.
+
+ if (t2l>0) {
+ dupecheck_aprs( digi->dupechecker,
+ (const char *)tbuf,
+ t2l,
+ (const char *)pb->ax25data+2,
+ pb->ax25datalen-2 ); // ignore Ctrl+PID
+ } else {
+ dupecheck_aprs( digi->dupechecker,
+ (const char *)state.ax25addr,
+ state.ax25addrlen,
+ (const char *)pb->ax25data+2,
+ pb->ax25datalen-2 ); // ignore Ctrl+PID
+ }
+ }
+
+ // Feed to interface_transmit_ax25() with new header and body
+ interface_transmit_ax25( digi->transmitter,
+ state.ax25addr, state.ax25addrlen,
+ (const char*)pb->ax25data, pb->ax25datalen );
+ if (debug>1) printf("Done.\n");
+}
+
+
+void digipeater_receive( struct digipeater_source *src,
+ struct pbuf_t *pb )
+{
+ // Below numbers like "4)" refer to Requirement Specification
+ // paper chapter 2.6: Digipeater Rules
+
+ // The dupe-filter exists for APRS frames, possibly for some
+ // selected UI frame types, and definitely not for CONS frames.
+
+ if (debug)
+ printf("digipeater_receive() from %s, is_aprs=%d viscous_delay=%d\n",
+ src->src_if->callsign, pb->is_aprs, src->viscous_delay);
+
+ if (src->tokenbucket < 1.0) {
+ if (debug) printf("SOURCE RATELIMIT DISCARD\n");
+ return;
+ }
+ src->tokenbucket -= 1.0;
+
+
+ if (pb->is_aprs) {
+
+ const int source_is_transmitter = (src->src_if ==
+ src->parent->transmitter);
+
+ // 1) Feed to dupe-filter (transmitter specific)
+ // If the dupe detector on this packet has reached
+ // count > 1, drop it.
+
+ int jittery = src->viscous_delay > 0 ? random() % 3 + src->viscous_delay : 0;
+ dupe_record_t *dupe = dupecheck_pbuf( src->parent->dupechecker,
+ pb, jittery);
+ if (dupe == NULL) { // Oops.. allocation error!
+ if (debug)
+ printf("digipeater_receive() - dupecheck_pbuf() allocation error, packet discarded\n");
+ return;
+ }
+
+ // 1.1) optional viscous delay!
+
+ if (src->viscous_delay == 0) { // No delay, direct cases
+
+ // First packet on direct source arrives here
+ // with seen = 1
+
+ // 1.x) Analyze dupe checking
+
+ if (debug>1)
+ printf("Seen this packet %d times (delayed=%d)\n",
+ dupe->delayed_seen + dupe->seen,
+ dupe->delayed_seen);
+
+ if (dupe->seen > 1) {
+ // N:th direct packet, duplicate.
+ // Drop this direct packet.
+ if (debug>1) printf(".. discarded\n");
+ return;
+ }
+
+ if (dupe->seen == 1 && dupe->delayed_seen > 0 &&
+ dupe->pbuf == NULL) {
+ // First direct, but dupe record does not have
+ // pbuf anymore indicating that a delayed
+ // handling did process it sometime in past.
+ // Drop this direct packet.
+ if (debug>1) printf(".. discarded\n");
+ return;
+ }
+
+ if (dupe->seen == 1 && dupe->delayed_seen >= 0 &&
+ dupe->pbuf != NULL) {
+
+ // First direct, and pbuf exists in dupe record.
+ // It was added first to viscous queue, and
+ // a bit latter came this direct one.
+ // Remove one from viscous queue, and proceed
+ // with direct processing.
+
+ if (debug>1) printf(" .. discard dupe record, process immediately");
+
+ pbuf_put(dupe->pbuf);
+ dupe->pbuf = NULL;
+ dupe = NULL; // Do not do dupecheck_put() here!
+ }
+
+ } else { // src->viscous_delay > 0
+
+ // First packet on viscous source arrives here
+ // with dupe->delayed_seen = 1
+
+ // Has this been seen on direct channel?
+ if (dupe->seen > 0) {
+ // Already processed thru direct processing,
+ // no point in adding this to viscous delay queue
+ if (debug>1)
+ printf("Seen this packet %d times. Discarding it.\n",
+ dupe->delayed_seen + dupe->seen);
+ return;
+ }
+
+ // Depending on source definition, the transmitter is
+ // either non-viscous or viscous. We care about it
+ // only when the source is viscous:
+ if (source_is_transmitter)
+ dupe->seen_on_transmitter += 1;
+
+ if (dupe->delayed_seen > 1) {
+ // 2nd or more of same packet from delayed source
+ if (debug>1)
+ printf("Seen this packet %d times.\n",
+ dupe->delayed_seen + dupe->seen);
+
+ // If any of them is transmitter interface, then
+ // drop the queued packet, and drop current one.
+ if (dupe->seen_on_transmitter > 0) {
+
+ // If pbuf is on delayed queue, drop it.
+ if (dupe->pbuf != NULL) {
+ pbuf_put(dupe->pbuf);
+ dupe->pbuf = NULL;
+ dupe = NULL; // Do not do dupecheck_put() here!
+ }
+
+ }
+ if (debug>1) printf(".. discarded\n");
+ return;
+ }
+
+ // First time that we have seen this packet at all.
+ // Put the pbuf_t on viscous delay queue.. (Put
+ // this dupe_record_t there, and the pbuf_t pointer
+ // is already in that dupe_record_t.)
+ src->viscous_queue_size += 1;
+ if (src->viscous_queue_size > src->viscous_queue_space) {
+ src->viscous_queue_space += 16;
+ src->viscous_queue = realloc( src->viscous_queue,
+ sizeof(void*) *
+ src->viscous_queue_space );
+ }
+ src->viscous_queue[ src->viscous_queue_size -1 ]
+ = dupecheck_get(dupe);
+
+ if (debug) printf("%ld ENTER VISCOUS QUEUE: len=%d pbuf=%p\n",
+ tick.tv_sec, src->viscous_queue_size, pb);
+ return; // Put on viscous queue
+
+ }
+ }
+ // Send directly to backend
+ if (debug>1) printf(".. direct to processing\n");
+ digipeater_receive_backend(src, pb);
+}
+
+dupecheck_t *digipeater_find_dupecheck(const struct aprx_interface *aif)
+{
+ int i;
+ for (i = 0; i < digi_count; ++i) {
+ if (aif == digis[i]->transmitter)
+ return digis[i]->dupechecker;
+ }
+ return NULL;
+}
+
+static void digipeater_resettime(void *arg)
+{
+ struct timeval *tv = (struct timeval *)arg;
+ *tv = tick;
+}
+
+
+// Viscous queue processing needs poll digis <source>s for delayed actions
+int digipeater_prepoll(struct aprxpolls *app)
+{
+ int d, s;
+
+ if (tokenbucket_timer.tv_sec == 0) {
+ tokenbucket_timer = tick; // init this..
+ }
+
+ // If the time(2) has jumped around a lot,
+ // and we didn't get around to do our work, reset the timer.
+
+ if (time_reset) {
+ digipeater_resettime(&tokenbucket_timer);
+ }
+
+ if (tv_timercmp( &tokenbucket_timer, &tick ) <= 0) {
+ // Run the digipeater timer handling now
+ // Will also advance the timer!
+ if (debug>2) printf("digipeater_prepoll() run tokenbucket_timers\n");
+ tv_timeradd_seconds( &tokenbucket_timer, &tokenbucket_timer, TOKENBUCKET_INTERVAL);
+ run_tokenbucket_timers();
+ }
+
+ if (tv_timercmp( &tokenbucket_timer, &app->next_timeout ) <= 0) {
+ app->next_timeout = tokenbucket_timer;
+ }
+
+ // if (debug>2) printf("digipeater_prepoll - 1 - timeout millis=%d\n",aprxpolls_millis(app));
+
+ // Over all digipeaters..
+ for (d = 0; d < digi_count; ++d) {
+ struct digipeater *digi = digis[d];
+ // Over all sources in those digipeaters
+ for (s = 0; s < digi->sourcecount; ++s) {
+ struct timeval tv;
+ struct digipeater_source * src = digi->sources[s];
+ // If viscous delay is zero, there is no work...
+ // if (src->viscous_delay == 0)
+ // continue;
+ // Delay is non-zero, perhaps there is work?
+ if (src->viscous_queue_size == 0) // Empty queue
+ continue;
+ // First entry expires first
+ tv.tv_sec = src->viscous_queue[0]->t + src->viscous_delay;
+ tv.tv_usec = 0;
+ if (tv_timercmp(&app->next_timeout, &tv) > 0) {
+ app->next_timeout = tv;
+ // if (debug>2) printf("digipeater_prepoll - 2 - timeout millis=%d\n",aprxpolls_millis(app));
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void sourcecalltick(struct digipeater *digi);
+
+int digipeater_postpoll(struct aprxpolls *app)
+{
+ int d, s, i, donecount;
+
+ if (tv_timercmp(&tokenbucket_timer, &tick) < 0) {
+ tv_timeradd_seconds( &tokenbucket_timer, &tokenbucket_timer, TOKENBUCKET_INTERVAL);
+ run_tokenbucket_timers();
+ }
+
+ // Over all digipeaters..
+ for (d = 0; d < digi_count; ++d) {
+ struct digipeater *digi = digis[d];
+
+ // Over all sources in those digipeaters
+ for (s = 0; s < digi->sourcecount; ++s) {
+ struct digipeater_source * src = digi->sources[s];
+
+ // If viscous delay is zero, there is no work...
+ // if (src->viscous_delay == 0)
+ // continue;
+ // Delay is non-zero, perhaps there is work?
+ if (src->viscous_queue_size == 0) // Empty queue
+ continue;
+ // Feed backend from viscous queue
+ donecount = 0;
+ for (i = 0; i < src->viscous_queue_size; ++i) {
+ struct dupe_record_t *dupe = src->viscous_queue[i];
+ time_t t = dupe->t + src->viscous_delay;
+ if ((t - tick.tv_sec) <= 0) {
+ if (debug)printf("%ld LEAVE VISCOUS QUEUE: dupe=%p pbuf=%p\n",
+ tick.tv_sec, dupe, dupe->pbuf);
+ if (dupe->pbuf != NULL) {
+ // We send the pbuf from viscous queue, if it still is
+ // present in the dupe record. (For example direct sourced
+ // packets remove a packet from queued dupe record.)
+ digipeater_receive_backend(src, dupe->pbuf);
+
+ // Remove the delayed pbuf from this dupe record.
+ pbuf_put(dupe->pbuf);
+ dupe->pbuf = NULL;
+ }
+ dupecheck_put(dupe);
+ ++donecount;
+ } else {
+ break; // found a case we are not yet interested in.
+ }
+ }
+ if (donecount > 0) {
+ if (donecount >= src->viscous_queue_size) {
+ // All cleared
+ src->viscous_queue_size = 0;
+ } else {
+ // Compact the queue left after this processing round
+ i = src->viscous_queue_size - donecount;
+ memcpy(&src->viscous_queue[0],
+ &src->viscous_queue[donecount],
+ sizeof(void*) * i);
+ src->viscous_queue_size = i;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int run_tokenbucket_timers()
+{
+ int d, s;
+ // Over all digipeaters..
+ for (d = 0; d < digi_count; ++d) {
+ struct digipeater *digi = digis[d];
+
+ digi->tokenbucket += digi->tbf_increment;
+ if (digi->tokenbucket > digi->tbf_limit)
+ digi->tokenbucket = digi->tbf_limit;
+
+#ifndef DISABLE_IGATE
+ sourcecalltick(digi);
+#endif
+
+ // Over all sources in those digipeaters
+ for (s = 0; s < digi->sourcecount; ++s) {
+ struct digipeater_source * src = digi->sources[s];
+
+ src->tokenbucket += src->tbf_increment;
+ if (src->tokenbucket > src->tbf_limit)
+ src->tokenbucket = src->tbf_limit;
+
+ }
+ }
+
+ return 0;
+}
+
+#ifndef DISABLE_IGATE
+static void sourcecalltick(struct digipeater *digi)
+{
+ int i;
+ historydb_t *db = digi->historydb;
+ if (db == NULL) return; // Should never happen..
+
+ for (i = 0; i < HISTORYDB_HASH_MODULO; ++i) {
+ history_cell_t *c = db->hash[i];
+ for ( ; c != NULL; c = c->next ) {
+ c->tokenbucket += digi->src_tbf_increment;
+ if (c->tokenbucket > digi->src_tbf_limit)
+ c->tokenbucket = digi->src_tbf_limit;
+ }
+ }
+}
+#endif
+
+// An utility function that exists at GNU Libc..
+
+#if !defined(HAVE_MEMRCHR) && !defined(_FOR_VALGRIND_)
+void *memrchr(const void *s, int c, size_t n) {
+ const unsigned char *p = s;
+ c &= 0xFF;
+ for (p = s+n; n > 0; --n, --p) {
+ if (*p == c) return (void*)p;
+ }
+ return NULL;
+}
+#endif
diff --git a/doc/aprx-manual-pics.odp b/doc/aprx-manual-pics.odp
new file mode 100644
index 0000000..6412ea4
Binary files /dev/null and b/doc/aprx-manual-pics.odp differ
diff --git a/doc/aprx-manual.odt b/doc/aprx-manual.odt
new file mode 100644
index 0000000..d8cea5d
Binary files /dev/null and b/doc/aprx-manual.odt differ
diff --git a/doc/aprx-manual.pdf b/doc/aprx-manual.pdf
new file mode 100644
index 0000000..d8dcd6e
Binary files /dev/null and b/doc/aprx-manual.pdf differ
diff --git a/doc/aprx-requirement-specification.odt b/doc/aprx-requirement-specification.odt
new file mode 100644
index 0000000..4dbf8a5
Binary files /dev/null and b/doc/aprx-requirement-specification.odt differ
diff --git a/doc/aprx-requirement-specification.pdf b/doc/aprx-requirement-specification.pdf
new file mode 100644
index 0000000..bc3af9e
Binary files /dev/null and b/doc/aprx-requirement-specification.pdf differ
diff --git a/dprsgw.c b/dprsgw.c
new file mode 100644
index 0000000..4c42523
--- /dev/null
+++ b/dprsgw.c
@@ -0,0 +1,1094 @@
+/* **************************************************************** *
+ * *
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ * **************************************************************** */
+
+#include "aprx.h"
+
+#ifndef DISABLE_IGATE
+
+/*
+ * The DPRS RX Gateway
+ *
+ * Receive data from DPRS.
+ * Convert to 3rd-party frame.
+ * Send out to APRSIS and Digipeaters.
+ *
+ * http://www.aprs-is.net/DPRS.aspx
+ *
+ *
+ *
+ * GPSxyz -> APRS symbols mapping:
+ *
+ * http://www.aprs.org/symbols/symbolsX.txt
+ */
+
+typedef struct dprsgw_history {
+ time_t gated;
+ char callsign[10];
+} dprsgw_history_t;
+
+// Up to 30 history entries to not to send same callsign too often
+#define HISTORYSIZE 30
+
+typedef struct dprs_gw {
+ char *ggaline;
+ char *rmcline;
+ int ggaspace;
+ int rmcspace;
+
+ int historylimit; // Time limit in seconds
+ dprsgw_history_t history[HISTORYSIZE];
+} dprsgw_t;
+
+
+// The dprslog() logs ONLY when '-d' mode is running.
+// .. and it will be removed soon.
+
+const char *dprslogfile;
+
+void dprslog( const time_t stamp, const uint8_t *buf ) {
+ if (dprslogfile == NULL) return; // Nothing to do
+
+ FILE *fp = fopen(dprslogfile,"a");
+ if (fp != NULL) {
+ fprintf(fp, "%ld\t%s\n", stamp, (const char *)buf);
+ fclose(fp);
+ }
+}
+
+
+static void dprsgw_flush(dprsgw_t *dp) {
+ if (dp->ggaline == NULL) {
+ dp->ggaspace = 200;
+ dp->ggaline = malloc(200);
+ }
+ if (dp->rmcline == NULL) {
+ dp->rmcspace = 200;
+ dp->rmcline = malloc(200);
+ }
+ dp->ggaline[0] = 0;
+ dp->rmcline[0] = 0;
+}
+
+static void *dprsgw_new(int historylimit) {
+ dprsgw_t *dp = calloc(1, sizeof(*dp));
+ dp->historylimit = historylimit;
+ dprsgw_flush(dp); // init buffers
+ return dp;
+}
+
+// Ratelimit returns 0 for "can send", 1 for "too soon"
+static int dprsgw_ratelimit( dprsgw_t *dp, const void *tnc2buf ) {
+ int i, n;
+ char callsign[10];
+ time_t expiry = tick.tv_sec - dp->historylimit;
+
+ memcpy(callsign, tnc2buf, sizeof(callsign));
+ callsign[sizeof(callsign)-1] = 0;
+ for (i = 0; i < sizeof(callsign); ++i) {
+ char c = callsign[i];
+ if (c == '>') {
+ callsign[i] = 0;
+ break;
+ }
+ }
+ n = -1;
+ for (i = 0; i < HISTORYSIZE; ++i) {
+
+ // Is there an entry?
+ if (dp->history[i].callsign[0] == 0)
+ continue;
+
+ if ((dp->history[i].gated - tick.tv_sec) > 0) {
+ // system time has jumped backwards, expire it.
+ dp->history[i].gated = expiry;
+ }
+ if ((dp->history[i].gated - expiry) > 0) {
+ // Fresh enough to be interesting!
+ if (strcmp(dp->history[i].callsign, callsign) == 0) {
+ // This callsign!
+ return 1;
+ }
+ } else {
+ dp->history[i].callsign[0] = 0; // discard it..
+ if (n < 0)
+ n = i; // save first free slot's index
+ }
+ }
+ if (n >= 0) {
+ memcpy(dp->history[n].callsign, callsign, sizeof(callsign));
+ dp->history[n].gated = tick.tv_sec;
+ }
+ return 0;
+}
+
+typedef struct gps2apr_syms {
+ const char gps[3];
+ const char aprs[3];
+ int flags;
+} gps2aprs_syms_t;
+
+// FIXME: Some symbols have 3 characters,
+// others take 3rd as overlay...
+// Add control flags below!
+
+static const gps2aprs_syms_t gps2aprsSyms[] = {
+ { "A0", "\\0", 0 },
+ { "A1", "\\1", 0 },
+ { "A2", "\\2", 0 },
+ { "A3", "\\3", 0 },
+ { "A4", "\\4", 0 },
+ { "A5", "\\5", 0 },
+ { "A6", "\\6", 0 },
+ { "A7", "\\7", 0 },
+ { "A8", "\\8", 0 },
+ { "A9", "\\9", 0 },
+ { "AA", "\\A", 0 },
+ { "AB", "\\B", 0 },
+ { "AC", "\\C", 0 },
+ { "AD", "\\D", 0 },
+ { "AE", "\\E", 0 },
+ { "AF", "\\F", 0 },
+ { "AG", "\\G", 0 },
+ { "AH", "\\H", 0 },
+ { "AI", "\\I", 0 },
+ { "AJ", "\\J", 0 },
+ { "AK", "\\K", 0 },
+ { "AL", "\\L", 0 },
+ { "AM", "\\M", 0 },
+ { "AN", "\\N", 0 },
+ { "AO", "\\O", 0 },
+ { "AP", "\\P", 0 },
+ { "AQ", "\\Q", 0 },
+ { "AR", "\\R", 0 },
+ { "AS", "\\S", 0 },
+ { "AT", "\\T", 0 },
+ { "AU", "\\U", 0 },
+ { "AV", "\\V", 0 },
+ { "AW", "\\W", 0 },
+ { "AX", "\\X", 0 },
+ { "AY", "\\Y", 0 },
+ { "AZ", "\\Z", 0 },
+ { "BB", "/!", 0 },
+ { "BC", "/\"", 0 },
+ { "BD", "/#", 0 },
+ { "BE", "/$", 0 },
+ { "BF", "/%", 0 },
+ { "BG", "/&", 0 },
+ { "BH", "/'", 0 },
+ { "BI", "/(", 0 },
+ { "BJ", "/)", 0 },
+ { "BK", "/*", 0 },
+ { "BL", "/+", 0 },
+ { "BM", "/,", 0 },
+ { "BN", "/-", 0 },
+ { "BO", "/.", 0 },
+ { "BP", "//", 0 },
+ { "DS", "\\[", 0 },
+ { "DT", "\\\\", 0 },
+ { "DU", "\\]", 0 },
+ { "DV", "\\^", 0 },
+ { "DW", "\\_", 0 },
+ { "DX", "\\`", 0 },
+ { "HS", "/[", 0 },
+ { "HT", "/\\", 0 },
+ { "HU", "/]", 0 },
+ { "HV", "/^", 0 },
+ { "HW", "/_", 0 },
+ { "HX", "/`", 0 },
+ { "J1", "/{", 0 },
+ { "J2", "/|", 0 },
+ { "J3", "/}", 0 },
+ { "J4", "/~", 0 },
+ { "LA", "/a", 0 },
+ { "LB", "/b", 0 },
+ { "LC", "/c", 0 },
+ { "LD", "/d", 0 },
+ { "LE", "/e", 0 },
+ { "LF", "/f", 0 },
+ { "LG", "/g", 0 },
+ { "LH", "/h", 0 },
+ { "LI", "/i", 0 },
+ { "LJ", "/j", 0 },
+ { "LK", "/k", 0 },
+ { "LL", "/l", 0 },
+ { "LM", "/m", 0 },
+ { "LN", "/n", 0 },
+ { "LO", "/o", 0 },
+ { "LP", "/p", 0 },
+ { "LQ", "/q", 0 },
+ { "LR", "/r", 0 },
+ { "LS", "/s", 0 },
+ { "LT", "/t", 0 },
+ { "LU", "/u", 0 },
+ { "LV", "/v", 0 },
+ { "LW", "/w", 0 },
+ { "LX", "/x", 0 },
+ { "LY", "/y", 0 },
+ { "LZ", "/z", 0 },
+ { "MR", "/:", 0 },
+ { "MS", "/;", 0 },
+ { "MT", "/<", 0 },
+ { "MU", "/=", 0 },
+ { "MV", "/>", 0 },
+ { "MW", "/?", 0 },
+ { "MX", "/@", 0 },
+ { "NR", "\\:", 0 },
+ { "NS", "\\;", 0 },
+ { "NT", "\\<", 0 },
+ { "NU", "\\=", 0 },
+ { "NV", "\\>", 0 },
+ { "NW", "\\?", 0 },
+ { "NX", "\\@", 0 },
+ { "OB", "\\!", 0 },
+ { "OC", "\\\"", 0 },
+ { "OD", "\\#", 0 },
+ { "OE", "\\$", 0 },
+ { "OF", "\\%", 0 },
+ { "OG", "\\&", 0 },
+ { "OH", "\\'", 0 },
+ { "OI", "\\(", 0 },
+ { "OJ", "\\)", 0 },
+ { "OK", "\\*", 0 },
+ { "OL", "\\+", 0 },
+ { "OM", "\\,", 0 },
+ { "ON", "\\-", 0 },
+ { "OO", "\\.", 0 },
+ { "OP", "\\/", 0 },
+ { "P0", "/0", 0 },
+ { "P1", "/1", 0 },
+ { "P2", "/2", 0 },
+ { "P3", "/3", 0 },
+ { "P4", "/4", 0 },
+ { "P5", "/5", 0 },
+ { "P6", "/6", 0 },
+ { "P7", "/7", 0 },
+ { "P8", "/8", 0 },
+ { "P9", "/9", 0 },
+ { "PA", "/A", 0 },
+ { "PB", "/B", 0 },
+ { "PC", "/C", 0 },
+ { "PD", "/D", 0 },
+ { "PE", "/E", 0 },
+ { "PF", "/F", 0 },
+ { "PG", "/G", 0 },
+ { "PH", "/H", 0 },
+ { "PI", "/I", 0 },
+ { "PJ", "/J", 0 },
+ { "PK", "/K", 0 },
+ { "PL", "/L", 0 },
+ { "PM", "/M", 0 },
+ { "PN", "/N", 0 },
+ { "PO", "/O", 0 },
+ { "PP", "/P", 0 },
+ { "PQ", "/Q", 0 },
+ { "PR", "/R", 0 },
+ { "PS", "/S", 0 },
+ { "PT", "/T", 0 },
+ { "PU", "/U", 0 },
+ { "PV", "/V", 0 },
+ { "PW", "/W", 0 },
+ { "PX", "/X", 0 },
+ { "PY", "/Y", 0 },
+ { "PZ", "/Z", 0 },
+ { "Q1", "\\{", 0 },
+ { "Q2", "\\|", 0 },
+ { "Q3", "\\}", 0 },
+ { "Q4", "\\~", 0 },
+ { "SA", "\\a", 0 },
+ { "SB", "\\b", 0 },
+ { "SC", "\\c", 0 },
+ { "SD", "\\d", 0 },
+ { "SE", "\\e", 0 },
+ { "SF", "\\f", 0 },
+ { "SG", "\\g", 0 },
+ { "SH", "\\h", 0 },
+ { "SI", "\\i", 0 },
+ { "SJ", "\\j", 0 },
+ { "SK", "\\k", 0 },
+ { "SL", "\\l", 0 },
+ { "SM", "\\m", 0 },
+ { "SN", "\\n", 0 },
+ { "SO", "\\o", 0 },
+ { "SP", "\\p", 0 },
+ { "SQ", "\\q", 0 },
+ { "SR", "\\r", 0 },
+ { "SS", "\\s", 0 },
+ { "ST", "\\t", 0 },
+ { "SU", "\\u", 0 },
+ { "SV", "\\v", 0 },
+ { "SW", "\\w", 0 },
+ { "SX", "\\x", 0 },
+ { "SY", "\\y", 0 },
+ { "SZ", "\\z", 0 },
+};
+
+static int gps2aprs_syms_count = sizeof(gps2aprsSyms) / sizeof(gps2aprsSyms[0]);
+
+static int parse_gps2aprs_symbol(const char *gpsxxx, char *aprssymbol) {
+ int i, mid, high, low;
+ char gps[3];
+ gps[0] = gpsxxx[0];
+ gps[1] = gpsxxx[1];
+ gps[2] = 0;
+ low = 0;
+ high = gps2aprs_syms_count-1;
+ while (low < high) {
+ mid = (low + high) >> 1; // divide by 2
+ i = strcmp(gps, gps2aprsSyms[mid].gps);
+ // if (debug) printf("GPS2APRS: '%s' '%s', low=%d mid=%d high=%d\n",gps, gps2aprsSyms[mid].gps, low, mid, high);
+ if (i == 0) {
+ // Exact match
+ char c3 = gpsxxx[2];
+ strcpy(aprssymbol, gps2aprsSyms[mid].aprs);
+ if (c3 != 0 && c3 != ' ' && aprssymbol[0] != '/') {
+ // FIXME: overlay ???
+ aprssymbol[0] = c3;
+ }
+ return 0;
+ }
+ if (i > 0) {
+ low = mid+1;
+ } else {
+ high = mid-1;
+ }
+ }
+ return i;
+}
+
+
+// The "Specification" says to use this checksum method..
+// It uses right-left inverted version of the polynome
+// of CCITT-CRC-16 but in the end it INVERTS the result!
+// Thus the result is NOT CCITT-CRC-16, but something
+// uniquely ICOM D-STAR..
+/*
+static int dprsgw_crccheck( const uint8_t *s, int len )
+{
+ int icomcrc = 0xffff;
+ for ( ; len > 0; ++s, --len) {
+ uint8_t ch = *s;
+ int i;
+ for (i = 0; i < 8; i++) {
+ int xorflag = (icomcrc ^ ch) & 0x01;
+ icomcrc >>= 1;
+ if (xorflag)
+ icomcrc ^= 0x8408;
+ ch >>= 1;
+ }
+ }
+ return (~icomcrc) & 0xffff;
+}
+*/
+
+static int dprsgw_isvalid( struct serialport *S )
+{
+ int i;
+
+ if (S->rdlinelen < 20) {
+ if (debug) printf("Too short a line for DPRS");
+ return 0; // definitely not!
+ }
+
+ if (memcmp("$$CRC", S->rdline, 5) == 0 && S->rdline[9] == ',') {
+ // Maybe a $$CRCB727,OH3BK-D>APRATS,DSTAR*:@165340h6128.23N/02353.52E-D-RATS (GPS-A) /A=000377
+ int crc;
+ int csum = -1;
+ int crc16;
+
+ S->rdline[S->rdlinelen] = '\r';
+ crc16 = calc_crc_ccitt(0xFFFF, S->rdline+10, S->rdlinelen+1-10); // INCLUDE the CR on CRC calculation!
+ crc = (crc16 ^ 0xFFFF); // Output is INVERTED
+
+ S->rdline[S->rdlinelen] = 0;
+ i = sscanf((const char*)(S->rdline), "$$CRC%04x,", &csum);
+ if (i != 1 || csum != crc) {
+ if (debug) printf("Bad DPRS APRS CRC: l=%d, i=%d, %04x/%04x vs. %s\n", S->rdlinelen, i, crc, csum, S->rdline);
+ // return 0;
+ } else {
+ if (debug>1) printf("$$CRC DSTAR=%04x CCITT-X25-FCS=%04x\n", csum, crc16);
+
+ if (debug) printf("Good DPRS APRS CRC: l=%d, i=%d, %04x/%04x vs. %s\n", S->rdlinelen, i, crc, csum, S->rdline);
+ return 1;
+ }
+ return 0;
+
+ } else if (memcmp("$GP", S->rdline, 3) == 0) {
+ // Maybe $GPRMC,170130.02,A,6131.6583,N,02339.1552,E,0.00,154.8,290510,6.5,E,A*02 ?
+ int xor = 0;
+ int csum = -1;
+ char c;
+ // if (debug) printf("NMEA: '%s'\n", S->rdline);
+ for (i = 1; i < S->rdlinelen; ++i) {
+ c = S->rdline[i];
+ if (c == '*' && (i >= S->rdlinelen - 3)) {
+ break;
+ }
+ xor ^= c;
+ }
+ xor &= 0xFF;
+ if (i != S->rdlinelen -3 || S->rdline[i] != '*')
+ return 0; // Wrong place to stop
+ if (sscanf((const char *)(S->rdline+i), "*%02x%c", &csum, &c) != 1) {
+ return 0; // Too little or too much
+ }
+ if (xor != csum) {
+ if (debug) printf("Bad DPRS $GP... checksum: %02x vs. %02x\n", csum, xor);
+ return 0;
+ }
+ return 1;
+ } else {
+ int xor = 0;
+ int csum = -1;
+ char c;
+ // .. uh? maybe? Precisely 29 characters:
+ // "OH3KGR M, "
+ if (S->rdlinelen != 29 || S->rdline[8] != ',') {
+ if (debug) printf("Bad DPRS identification(?) packet - length(%d) != 29 || line[8] != ',': %s\n",
+ S->rdlinelen, S->rdline);
+ return 0;
+ }
+ if (debug) printf("DPRS NMEA: '%s'\n", S->rdline);
+ for (i = 0; i < S->rdlinelen; ++i) {
+ c = S->rdline[i];
+ if (c == '*') {
+ break;
+ }
+ xor ^= c;
+ }
+ xor &= 0xFF;
+ if (sscanf((const char *)(S->rdline+i), "*%x%c", &csum, &c) < 1) {
+ if (memcmp(S->rdline+8, ", ", 21) == 0) {
+ if (debug) printf("DPRS IDENT LINE OK: '%s'\n", S->rdline);
+ return 1;
+ }
+ // if (debug) printf("csum bad NMEA: '%s'\n", S->rdline);
+ return 0; // Too little or too much
+ }
+ if (xor != csum) {
+ if (debug) printf("Bad DPRS IDENT LINE checksum: %02x vs. %02x\n", csum, xor);
+ return 0;
+ }
+ if (debug) printf("DPRS IDENT LINE OK: '%s'\n", S->rdline);
+ // if (debug) printf("csum valid NMEA: '%s'\n", S->rdline);
+ return 1; // Maybe valid?
+ }
+}
+
+
+// Split NMEA text line at ',' characters
+static int dprsgw_nmea_split(char *nmea, char *fields[], int n) {
+ int i = 0;
+ --n;
+ fields[i] = nmea;
+ for ( ; *nmea; ++nmea ) {
+ for ( ; *nmea != 0 && *nmea != ','; ++nmea )
+ ;
+ if (*nmea == 0) break; // THE END!
+ if (*nmea == ',')
+ *nmea++ = 0; // COMMA terminates a field, change to SPACE
+ if (i < n) ++i; // Prep next field index
+ fields[i] = nmea; // save field pointer
+ }
+ fields[i] = NULL;
+ return i;
+}
+
+static void dprsgw_nmea_igate( const struct aprx_interface *aif,
+ const uint8_t *ident, dprsgw_t *dp ) {
+ int i;
+ char *gga[20];
+ char *rmc[20];
+ char tnc2buf[2000];
+ int tnc2addrlen;
+ int tnc2buflen;
+ char *p, *p2;
+ const char *p0;
+ const char *s;
+ int alt_feet = -9999999;
+ char aprssym[3];
+
+ if (debug) {
+ printf(" DPRS: ident='%s', GGA='%s', RMC='%s'\n", ident, dp->ggaline, dp->rmcline);
+ }
+
+ strcpy(aprssym, "/>"); // Default..
+ parse_gps2aprs_symbol((const char *)ident+9, aprssym);
+
+ memset(gga, 0, sizeof(gga));
+ memset(rmc, 0, sizeof(rmc));
+
+ // $GPGGA,hhmmss.dd,xxmm.dddd,<N|S>,yyymm.dddd,<E|W>,v,
+ // ss,d.d,h.h,M,g.g,M,a.a,xxxx*hh<CR><LF>
+
+ // $GPRMC,hhmmss.dd,S,xxmm.dddd,<N|S>,yyymm.dddd,<E|W>,
+ // s.s,h.h,ddmmyy,d.d, <E|W>,M*hh<CR><LF>
+ // ,S, = Status: 'A' = Valid, 'V' = Invalid
+
+ if (dp->ggaline[0] != 0)
+ dprsgw_nmea_split(dp->ggaline, gga, 20);
+ if (dp->rmcline[0] != 0)
+ dprsgw_nmea_split(dp->rmcline, rmc, 20);
+
+ if (rmc[2] != NULL && strcmp(rmc[2],"A") != 0) {
+ if (debug) printf("Invalid DPRS $GPRMC packet (validity='%s')\n",
+ rmc[2]);
+ return;
+ }
+ if (gga[6] != NULL && strcmp(gga[6],"1") != 0) {
+ if (debug) printf("Invalid DPRS $GPGGA packet (validity='%s')\n",
+ gga[6]);
+ return;
+ }
+ if (dp->ggaline[0] == 0 && dp->rmcline[0] == 0) {
+ if (debug) printf("No DPRS $GPRMC nor $GPGGA packets available.\n");
+ return;
+ }
+
+ p = tnc2buf;
+ for (i = 0; i < 8; ++i) {
+ if (ident[i] == ' ') {
+ *p++ = '-';
+ for ( ; ident[i+1] == ' ' && i < 8; ++i );
+ continue;
+ }
+ *p++ = ident[i];
+ }
+
+ if (p > tnc2buf && p[-1] == '-') --p;
+
+ p += sprintf(p, ">APDPRS,DSTAR*");
+
+
+ tnc2addrlen = p - tnc2buf;
+ *p++ = ':';
+ *p++ = '!'; // Std position w/o messaging
+ p2 = p;
+ if (gga[2] != NULL) {
+ s = gga[2];
+ } else if (rmc[3] != NULL) {
+ s = rmc[3];
+ } else {
+ // No coordinate!
+ if (debug) printf("dprsgw: neither GGA nor RMC coordinates available, discarding!\n");
+ return;
+ }
+ p0 = strchr(s, '.');
+ if (!p0) {
+ if (debug) printf("dprsgw: invalid format NMEA North coordinate: '%s'\n", s);
+ return;
+ }
+ while (p0 - s < 4) {
+ *p++ = '0';
+ ++p0; // move virtually
+ }
+ p += sprintf(p, "%s", s);
+ if (p2+7 < p) { // Too long!
+ p = p2+7;
+ }
+ while (p2+7 > p) { // Too short!
+ *p++ = ' '; // unprecise position
+ }
+ if (gga[2] != NULL) {
+ s = gga[3]; // <N|S>
+ } else if (rmc[3] != NULL) {
+ s = rmc[4]; // <N|S>
+ }
+ p += sprintf(p, "%s", s);
+
+ *p++ = aprssym[0];
+ p2 = p;
+ if (gga[2] != NULL) {
+ s = gga[4]; // yyymm.dddd
+ } else if (rmc[3] != NULL) {
+ s = rmc[5]; // yyymm.dddd
+ }
+ p0 = strchr(s, '.');
+ if (!p0) {
+ if (debug) printf("dprsgw: invalid format NMEA East coordinate: '%s'\n", s);
+ return;
+ }
+ while (p0 - s < 5) {
+ *p++ = '0';
+ ++p0; // move virtually
+ }
+
+ p += sprintf(p, "%s", s);
+ if (p2+8 < p) { // Too long!
+ p = p2+8;
+ }
+ while (p2+8 > p) { // Too short!
+ *p++ = ' '; // unprecise position
+ }
+ if (gga[2] != NULL) {
+ p += sprintf(p, "%s", gga[5]); // <E|W>
+ } else if (rmc[3] != NULL) {
+ p += sprintf(p, "%s", rmc[6]); // <E|W>
+ }
+
+ *p++ = aprssym[1];
+
+ // DPRS: ident='OH3BK D,BN *59 ', GGA='$GPGGA,204805,6128.230,N,2353.520,E,1,3,0,115,M,0,M,,*6d', RMC=''
+ // DPRSGW GPS data: OH3BK-D>APDPRS,DSTAR*:!6128.23N/02353.52E>
+
+ if (gga[2] != NULL) {
+ if (gga[9] != NULL && gga[9][0] != 0)
+ alt_feet = strtol(gga[9], NULL, 10);
+ if (strcmp(gga[10],"M") == 0) {
+ // Meters! Convert to feet..
+ alt_feet = (10000 * alt_feet) / 3048;
+ } else {
+ // Already feet - presumably
+ }
+ }
+
+ // FIXME: more data!
+ // RMC HEADING/SPEED
+
+ p0 = (const char *)ident + 29;
+ s = (const char *)ident + 9+4;
+ p2 = p;
+
+ for ( ; s < p0; ++s ) {
+ if (*s != ' ')
+ break;
+ }
+ if (s < p0)
+ *p++ = ' ';
+ for ( ; s < p0; ++s ) {
+ if (*s == '*')
+ break;
+ *p++ = *s;
+ }
+ if (p > p2)
+ *p++ = ' ';
+
+ if (alt_feet > -9999999) {
+ p += sprintf(p, "/A=%06d", alt_feet);
+ }
+
+ *p = 0;
+ tnc2buflen = p - tnc2buf;
+
+ if (debug) printf("DPRSGW GPS data: %s\n", tnc2buf);
+
+ if (!dprsgw_ratelimit( dp, tnc2buf )) {
+
+ char *fromcall, *origtocall;
+ char *b;
+
+ if (aif != NULL) {
+ igate_to_aprsis( aif->callsign, 0, (const char *)tnc2buf, tnc2addrlen, tnc2buflen, 0, 0);
+ // Bytes have been counted previously, now count meaningful packet
+ erlang_add(aif->callsign, ERLANG_RX, 0, 1);
+ }
+
+ fromcall = tnc2buf;
+ p = fromcall;
+ origtocall = NULL;
+ while (*p != '>' && *p != 0) ++p;
+ if (*p == '>') {
+ *p++ = 0;
+ origtocall = p;
+ } else
+ return; // BAD :-(
+ p = origtocall;
+ while (p != NULL && *p != ':' && *p != 0 && *p != ',') ++p;
+ if (p != NULL && (*p == ':' || *p == ',')) {
+ *p++ = 0;
+ }
+ b = tnc2buf + tnc2addrlen +1;
+ interface_receive_3rdparty( aif,
+ fromcall, origtocall, "DSTAR*",
+ b, tnc2buflen - (b-tnc2buf) );
+ }
+}
+
+static void dprsgw_rxigate( struct serialport *S )
+{
+ const struct aprx_interface *aif = S->interface[0];
+ uint8_t *tnc2addr = S->rdline;
+ int tnc2addrlen = S->rdlinelen;
+ uint8_t *tnc2body = S->rdline;
+ int tnc2bodylen = S->rdlinelen;
+
+#ifndef DPRSGW_DEBUG_MAIN
+ if (aif == NULL) {
+ if (debug) printf("OOPS! NO <interface> ON DPRS SERIAL PORT! BUG!\n");
+ return;
+ }
+#endif
+
+ if (memcmp("$$CRC", tnc2addr, 5) == 0 && tnc2addrlen > 20) {
+ tnc2addr += 10;
+ tnc2addrlen -= 10;
+ tnc2bodylen -= 10; // header + body together
+ tnc2body = memchr( tnc2addr, ':', tnc2addrlen);
+ if (tnc2body != NULL) {
+ char *fromcall, *origtocall;
+ char *s;
+
+ tnc2addrlen = tnc2body - tnc2addr;
+ ++tnc2body;
+
+ if (dprsgw_ratelimit(S->dprsgw, tnc2addr)) {
+ // Rate-limit ordered rejection
+ return;
+ }
+
+ // Acceptable packet, Rx-iGate it!
+ igate_to_aprsis( aif->callsign, 0, (const char *)tnc2addr, tnc2addrlen, tnc2bodylen, 0, 0);
+ // Bytes have been counted previously, now count meaningful packet
+ erlang_add( aif->callsign, ERLANG_RX, 0, 1 );
+
+ fromcall = (char*)tnc2addr;
+ s = fromcall;
+ origtocall = NULL;
+ while (*s != '>' && *s != 0) ++s;
+ if (*s == '>') {
+ *s++ = 0;
+ origtocall = s;
+ } else
+ return; // BAD :-(
+ s = origtocall;
+ while (s != NULL && *s != ':' && *s != 0 && *s != ',') ++s;
+ if (s != NULL && (*s == ':' || *s == ',')) {
+ *s++ = 0;
+ }
+
+ interface_receive_3rdparty( aif,
+ fromcall, origtocall, "DSTAR*",
+ (const char*)tnc2body, tnc2bodylen - (tnc2body-tnc2addr) );
+ return;
+
+ } else {
+ // Bad packet!
+ if (debug) printf("Bad DPRS packet! %s\n", S->rdline);
+ return;
+ }
+
+ } else if (memcmp("$GPGGA,", tnc2addr, 7) == 0) {
+ dprsgw_t *dp = S->dprsgw;
+
+ if (dp->ggaspace <= S->rdlinelen) {
+ dp->ggaline = realloc(dp->ggaline, S->rdlinelen+1);
+ dp->ggaspace = S->rdlinelen;
+ }
+ memcpy(dp->ggaline, tnc2addr, tnc2bodylen);
+ dp->ggaline[tnc2bodylen] = 0;
+ if (debug) printf("DPRS GGA: %s\n", dp->ggaline);
+
+ } else if (memcmp("$GPRMC,", tnc2addr, 7) == 0) {
+ dprsgw_t *dp = S->dprsgw;
+
+ if (dp->rmcspace <= S->rdlinelen) {
+ dp->rmcline = realloc(dp->rmcline, S->rdlinelen+1);
+ dp->rmcspace = S->rdlinelen;
+ }
+ memcpy(dp->rmcline, tnc2addr, tnc2bodylen);
+ dp->rmcline[tnc2bodylen] = 0;
+ if (debug) printf("DPRS RMC: %s\n", dp->rmcline);
+
+ } else if (tnc2addr[8] == ',' && tnc2bodylen == 29) {
+ // Acceptable DPRS "ident" line
+ dprsgw_t *dp = S->dprsgw;
+ tnc2addr[tnc2bodylen] = 0; // zero-terminate just in case
+ dprsgw_nmea_igate(aif, tnc2addr, dp);
+
+ } else {
+ // this should never be called...
+ if (debug) printf("Unrecognized DPRS packet: %s\n", S->rdline);
+ return;
+ }
+}
+
+/*
+ * Receive one text line from serial port
+ * It will end with 0x00 byte, and not contain \r nor \n.
+ *
+ * It MAY have junk at the start.
+ *
+ *
+ */
+
+static void dprsgw_receive( struct serialport *S )
+{
+ int i;
+ uint8_t *p;
+
+ if (debug) dprslog(S->rdline_time, S->rdline);
+
+ do {
+ if (dprsgw_isvalid(S)) {
+ // Feed it to DPRS-APRS-GATE
+ dprsgw_rxigate( S );
+ return; // Done!
+ } else {
+ // Not a good packet! See if there is a good packet inside?
+ dprsgw_flush(S->dprsgw); // bad input -> discard accumulated data
+
+ p = memchr(S->rdline+1, '$', S->rdlinelen-1);
+ if (p == NULL)
+ break; // No '$' to start something
+ i = S->rdlinelen - (p - S->rdline);
+ if (i <= 0)
+ break; // exhausted!
+ S->rdlinelen = i;
+ memcpy(S->rdline, p, S->rdlinelen);
+ S->rdline[i] = 0;
+ continue;
+ }
+ } while(1);
+}
+
+
+/*
+ * Receive more data from DPRS type serial port
+ * This handles correct data accumulation and sync hunting
+ */
+int dprsgw_pulldprs( struct serialport *S )
+{
+ const time_t rdtime = S->rdline_time;
+ const struct aprx_interface *aif = S->interface[0];
+ int c;
+ int i;
+
+ // Account all received bytes, this may or may not be a packet
+ erlang_add(aif->callsign, ERLANG_RX, S->rdlinelen, 0);
+
+
+ if (S->dprsgw == NULL)
+ S->dprsgw = dprsgw_new(30); // FIXME: hard-coded 30 second delay for DPRS repeats
+
+ if ((rdtime+2 - tick.tv_sec) < 0) {
+ // A timeout has happen? (2 seconds!) Either data is added constantly,
+ // or nothing was received from DPRS datastream!
+
+ if (S->rdlinelen > 0)
+ if (debug)printf("dprsgw: previous data is %d sec old, discarding its state: %s\n",((int)(tick.tv_sec-rdtime)), S->rdline);
+
+ S->rdline[S->rdlinelen] = 0;
+ if (S->rdlinelen > 0 && debug) dprslog(rdtime, S->rdline);
+ S->rdlinelen = 0;
+
+ dprsgw_flush(S->dprsgw); // timeout -> discard accumulated data
+ }
+ S->rdline_time = tick.tv_sec;
+
+ for (i=0 ; ; ++i) {
+
+ c = ttyreader_getc(S);
+ if (c < 0) {
+ // if (debug) printf("dprsgw_pulldprs: read %d chars\n", i);
+ return c; /* Out of input.. */
+ }
+ if (debug>2) printf("DPRS %ld %3d %02X '%c'\n", tick.tv_sec, S->rdlinelen, c, c);
+
+ /* S->dprsstate != 0: read data into S->rdline,
+ == 0: discard data until CR|LF.
+ Zero-size read line is discarded as well
+ (only CR|LF on input frame) */
+
+ /* Looking for CR or LF.. */
+ if (c == '\n' || c == '\r') {
+ /* End of line seen! */
+ if (S->rdlinelen > 0) {
+
+ /* Non-zero-size string, put terminating 0 byte on it. */
+ S->rdline[S->rdlinelen] = 0;
+
+ dprsgw_receive(S);
+ }
+ S->rdlinelen = 0;
+ continue;
+ }
+ // A '$' starts possible data..
+ if (c == '$' && S->rdlinelen == 0) {
+ S->rdline[S->rdlinelen++] = c;
+ continue;
+ }
+ // More fits in?
+ if (S->rdlinelen >= sizeof(S->rdline)-3) {
+ // Too long a line...
+ do {
+ int len;
+ uint8_t *p;
+ dprsgw_flush(S->dprsgw);
+
+ // Look for first '$' in buffer _after_ first char
+ p = memchr(S->rdline+1, '$', S->rdlinelen-1);
+ if (!p) {
+ S->rdlinelen = 0;
+ break; // Not found
+ }
+ len = S->rdlinelen - (p - S->rdline);
+ if (len <= 0) {
+ S->rdlinelen = 0;
+ break; // exhausted
+ }
+ memcpy(S->rdline, p, len);
+ S->rdline[len] = 0;
+ S->rdlinelen = len;
+ if (len >= 3) {
+ if (memcmp("$$C", S->rdline, 3) != 0 &&
+ memcmp("$GP", S->rdline, 3) != 0) {
+ // Not acceptable 3-char prefix
+ // Eat away the collected prefixes..
+ continue;
+ }
+ }
+ break;
+ } while(1);
+ }
+ S->rdline[S->rdlinelen++] = c;
+
+/*
+ // Too short to say anything?
+ if (S->rdlinelen < 3) {
+ continue;
+ }
+ if (S->rdlinelen == 3 &&
+ (memcmp("$$C", S->rdline, 3) != 0 &&
+ memcmp("$GP", S->rdline, 3) != 0)) {
+ // No correct start, discard...
+ dprsgw_flush(S->dprsgw);
+ S->rdlinelen = 2;
+ memcpy(S->rdline, S->rdline+1, 2);
+ S->rdline[S->rdlinelen] = 0;
+ if (S->rdline[0] != '$') {
+ // Didn't start with a '$'
+ S->rdlinelen = 0;
+ }
+ continue;
+ }
+*/
+ } /* .. input loop */
+
+ return 0; /* not reached */
+}
+
+
+int dprsgw_prepoll(struct aprxpolls *app)
+{
+ return 0; // returns number of sockets filled (ignored at caller)
+}
+
+int dprsgw_postpoll(struct aprxpolls *app)
+{
+ return 0; // returns number of sockets filled (ignored at caller)
+}
+
+
+
+#ifdef DPRSGW_DEBUG_MAIN
+
+int freadln(FILE *fp, char *p, int buflen) // DPRSGW_DEBUG_MAIN
+{
+ int n = 0;
+ while (!feof(fp)) {
+ int c = fgetc(fp);
+ if (c == EOF) break;
+ if (n >= buflen) break;
+ *p++ = c;
+ ++n;
+ if (c == '\n') break;
+ if (c == '\r') break;
+ }
+ return n;
+}
+
+int ttyreader_getc(struct serialport *S) // DPRSGW_DEBUG_MAIN
+{
+ if (S->rdcursor >= S->rdlen) { /* Out of data ? */
+ if (S->rdcursor)
+ S->rdcursor = S->rdlen = 0;
+ /* printf("-\n"); */
+ return -1;
+ }
+
+ /* printf(" %02X", 0xFF & S->rdbuf[S->rdcursor++]); */
+
+ return (0xFF & S->rdbuf[S->rdcursor++]);
+}
+void igate_to_aprsis(const char *portname, const int tncid, const char *tnc2buf, int tnc2addrlen, int tnc2len, const int discard, const int strictax25_) // DPRSGW_DEBUG_MAIN
+{
+ printf("DPRS RX-IGATE: %s\n", tnc2buf);
+}
+
+void interface_receive_3rdparty(const struct aprx_interface *aif, const char *fromcall, const char *origtocall, const char *gwtype, const char *tnc2data, const int tnc2datalen) // DPRSGW_DEBUG_MAIN
+{
+ printf("DPRS 3RDPARTY RX: ....:}%s>%s,%s,GWCALLSIGN*:%s\n",
+ fromcall, origtocall, gwtype, tnc2data);
+}
+
+int debug = 3;
+struct timeval tick;
+int main(int argc, char *argv[]) {
+ struct serialport S;
+ memset(&S, 0, sizeof(S));
+
+#if 0
+ // A test where string has initially some incomplete data, then finally a real data
+ printf("\nFIRST TEST\n");
+ strcpy((void*)S.rdline, "x$x4$GPPP$$$GP $$CRCB727,OH3BK-D>$$CRCB727,OH3BK-D>APRATS,DSTAR*:@165340h6128.23N/02353.52E-D-RATS (GPS-A) /A=000377");
+ S.rdlinelen = strlen((void*)S.rdline);
+ dprsgw_receive(&S);
+
+ printf("\nSECOND TEST\n");
+ strcpy((void*)S.rdline, "\304\3559\202\333$$CRCC3F5,OH3KGR-M>API282,DSTAR*:/123035h6131.29N/02340.45E>/IC-E2820");
+ S.rdlinelen = strlen((void*)S.rdline);
+ dprsgw_receive(&S);
+
+ printf("\nTHIRD TEST\n");
+ strcpy((void*)S.rdline, "[SOB]\"=@=@=@=>7\310=@\010!~~~~~~~!~~~~~~~\001\001\001\001\001\001\001\001[EOB]$$CRCBFB7,OH3BK>APRATS,DSTAR*:@124202h6128.23N/02353.52E-D-RATS (GPS-A) /A=000377");
+ S.rdlinelen = strlen((void*)S.rdline);
+ dprsgw_receive(&S);
+
+ printf("\nTEST 4.\n");
+ strcpy((void*)S.rdline, "$GPGGA,164829.02,6131.6572,N,02339.1567,E,1,08,1.1,111.3,M,19.0,M,,*61");
+ S.rdlinelen = strlen((void*)S.rdline);
+ dprsgw_receive(&S);
+
+ printf("\nTEST 5.\n");
+ strcpy((void*)S.rdline, "$GPRMC,170130.02,A,6131.6583,N,02339.1552,E,0.00,154.8,290510,6.5,E,A*02");
+ // strcpy((void*)S.rdline, "$GPRMC,164830.02,A,6131.6572,N,02339.1567,E,0.00,182.2,290510,6.5,E,A*07");
+ S.rdlinelen = strlen((void*)S.rdline);
+ dprsgw_receive(&S);
+
+ printf("\nTEST 6.\n");
+ strcpy((void*)S.rdline, "OH3BK D,BN *59 ");
+ S.rdlinelen = strlen((void*)S.rdline);
+ dprsgw_receive(&S);
+#endif
+
+ S.ttyname = "testfile";
+ S.ttycallsign[0] = "OH2MQK-DR";
+ FILE *fp = fopen("tt.log", "r");
+ for (;;) {
+ char buf1[3000];
+ int n = freadln(fp, buf1, sizeof(buf1));
+ if (n == 0) break;
+ char *ep;
+ tick.tv_sec = strtol(buf1, &ep, 10); // test code time init
+ if (*ep == '\t') ++ep;
+ int len = n - (ep - buf1);
+ if (len > 0) {
+ memcpy(S.rdbuf+S.rdlen, ep, len);
+ S.rdlen += len;
+ }
+ if (S.rdlen > 0)
+ dprsgw_pulldprs(&S);
+
+ }
+ fclose(fp);
+
+ return 0;
+}
+#endif
+#endif
diff --git a/dupecheck.c b/dupecheck.c
new file mode 100644
index 0000000..d14c543
--- /dev/null
+++ b/dupecheck.c
@@ -0,0 +1,484 @@
+/* **************************************************************** *
+ * *
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ * **************************************************************** */
+
+#include "aprx.h"
+
+/*
+ * Some parts of this code are copied from:
+ */
+/*
+ * aprsc
+ *
+ * (c) Heikki Hannikainen, OH7LZB <hessu at hes.iki.fi>
+ *
+ * This program is licensed under the BSD license, which can be found
+ * in the file LICENSE.
+ *
+ */
+
+/*
+ * dupecheck.c: the dupe-checkers
+ */
+
+static int dupecheck_cellgauge;
+static int dupecheckers_count;
+static dupecheck_t **dupecheckers;
+
+
+#ifndef _FOR_VALGRIND_
+cellarena_t *dupecheck_cells;
+#endif
+
+const int duperecord_size = sizeof(struct dupe_record_t);
+const int duperecord_align = __alignof__(struct dupe_record_t);
+
+/*
+ * The cellmalloc does not need internal MUTEX, it is being used in single thread..
+ */
+
+void dupecheck_init(void)
+{
+#ifndef _FOR_VALGRIND_
+ dupecheck_cells = cellinit( "dupecheck",
+ duperecord_size,
+ duperecord_align,
+ CELLMALLOC_POLICY_LIFO | CELLMALLOC_POLICY_NOMUTEX,
+ 4 /* 4 kB at the time */,
+ 0 /* minfree */);
+#endif
+}
+
+/*
+ * dupecheck_new() creates a new instance of dupechecker
+ *
+ */
+dupecheck_t *dupecheck_new(const int storetime) {
+ dupecheck_t *dp = calloc(1, sizeof(dupecheck_t));
+
+ ++dupecheckers_count;
+ dupecheckers = realloc(dupecheckers,
+ sizeof(dupecheck_t *) * dupecheckers_count);
+ dupecheckers[ dupecheckers_count -1 ] = dp;
+
+ dp->storetime = storetime;
+
+ return dp;
+}
+
+
+static dupe_record_t *dupecheck_db_alloc(int alen, int pktlen)
+{
+ dupe_record_t *dp;
+#ifndef _FOR_VALGRIND_
+// if (debug) printf("DUPECHECK db alloc(alen=%d,dlen=%d) %s",
+// alen,pktlen, dupecheck_free ? "FreeChain":"CellMalloc");
+
+ dp = cellmalloc(dupecheck_cells);
+// if (debug) printf(" dp=%p\n",dp);
+ if (dp == NULL)
+ return NULL;
+ // cellmalloc() block may need separate pktbuf
+ memset(dp, 0, sizeof(*dp));
+ dp->packet = dp->packetbuf;
+ if (pktlen > sizeof(dp->packetbuf))
+ dp->packet = malloc(pktlen+1);
+#else
+ // directly malloced block is fine as is
+ dp = calloc(1, pktlen + sizeof(*dp));
+ dp->packet = dp->packetbuf; // always suitable size
+#endif
+ dp->alen = alen;
+ dp->plen = pktlen;
+
+ ++dupecheck_cellgauge;
+
+ dupecheck_get(dp); // increment refcount
+
+ // if(debug)printf("DUPECHECK db alloc() returning dp=%p\n",dp);
+ return dp;
+}
+
+static void dupecheck_db_free(dupe_record_t *dp)
+{
+ if (dp->pbuf != NULL) { // If a pbuf is referred, release it
+ pbuf_put(dp->pbuf); // decrements refcount - and frees at zero.
+ dp->pbuf = NULL;
+ }
+#ifndef _FOR_VALGRIND_
+ if (dp->packet != dp->packetbuf)
+ free(dp->packet);
+ cellfree(dupecheck_cells, dp);
+#else
+ free(dp);
+#endif
+ --dupecheck_cellgauge;
+}
+
+// Increment refcount
+dupe_record_t *dupecheck_get(dupe_record_t *dp)
+{
+ dp->refcount += 1;
+ return dp;
+}
+
+// Decrement refcount, when zero, call free
+void dupecheck_put(dupe_record_t *dp)
+{
+ dp->refcount -= 1;
+ if (dp->refcount <= 0) {
+ dupecheck_db_free(dp);
+ }
+}
+
+/* The dupecheck_cleanup() is for regular database cleanups,
+ * Call this about once a minute.
+ *
+ * Note: entry validity is possibly shorter time than the cleanup
+ * invocation interval!
+ */
+static void dupecheck_cleanup(void)
+{
+ dupe_record_t *dp, **dpp;
+ int cleancount = 0, i, d;
+
+ // All dupecheckers..
+ for (d = 0; d < dupecheckers_count; ++d) {
+
+ // Within this dupechecker...
+ struct dupecheck_t *dpc = dupecheckers[d];
+ for (i = 0; i < DUPECHECK_DB_SIZE; ++i) {
+ dpp = & (dpc->dupecheck_db[i]);
+ while (( dp = *dpp )) {
+ if ((dp->t_exp - tick.tv_sec) < 0) {
+ /* Old.. discard. */
+ *dpp = dp->next;
+ dp->next = NULL;
+ dupecheck_put(dp);
+ ++cleancount;
+ continue;
+ }
+ /* No expiry, just advance the pointer */
+ dpp = &dp->next;
+ }
+ }
+ }
+ // hlog( LOG_DEBUG, "dupecheck_cleanup() removed %d entries, count now %ld",
+ // cleancount, dupecheck_cellgauge );
+}
+
+/*
+ * Check a single packet for duplicates in APRS sense
+ * The addr/alen must be in TNC2 monitor format, data/dlen
+ * are expected to be APRS payload as well.
+ */
+
+dupe_record_t *dupecheck_aprs(dupecheck_t *dpc,
+ const char *addr, const int alen,
+ const char *data, const int dlen)
+{
+ /* check a single packet */
+ // pb->flags |= F_DUPE; /* this is a duplicate! */
+
+ int i;
+ int addrlen; // length of the address part
+ int datalen; // length of the payload
+ uint32_t hash, idx;
+ dupe_record_t **dpp, *dp;
+
+ // 1) collect canonic rep of the address (SRC,DEST, no VIAs)
+ i = 1;
+ for (addrlen = 0; addrlen < alen; ++ addrlen) {
+ const char c = addr[addrlen];
+ if (c == 0 || c == ',' || c == ':') {
+ break;
+ }
+ if (c == '-' && i) {
+ i = 0;
+ }
+ }
+
+ // code to prevent segmentation fault
+ if (addrlen > 18) {
+ if (debug>1) printf(" addrlen=\"%d\" > 18, discard packet\n",addrlen);
+ return NULL;
+ }
+
+ // Canonic tail has no SPACEs in data portion!
+ // TODO: how to treat 0 bytes ???
+ datalen = dlen;
+ while (datalen > 0 && data[datalen-1] == ' ')
+ --datalen;
+
+ // there are no 3rd-party frames in APRS-IS ...
+
+ // 2) calculate checksum (from disjoint memory areas)
+
+ hash = keyhash(addr, addrlen, 0);
+ hash = keyhash(data, datalen, hash);
+ idx = hash;
+
+ // 3) lookup if same checksum is in some hash bucket chain
+ // 3b) compare packet...
+ // 3b1) flag as F_DUPE if so
+ // DUPECHECK_DB_SIZE == 16 -> 4 bits index
+ idx ^= (idx >> 16); /* fold the hash bits.. */
+ idx ^= (idx >> 8); /* fold the hash bits.. */
+ idx ^= (idx >> 4); /* fold the hash bits.. */
+ i = idx % DUPECHECK_DB_SIZE;
+ dpp = &(dpc->dupecheck_db[i]);
+ while (*dpp) {
+ dp = *dpp;
+ if ((dp->t_exp - tick.tv_sec) < 0) {
+ // Old ones are discarded when seen
+ *dpp = dp->next;
+ dp->next = NULL;
+ dupecheck_put(dp);
+ continue;
+ }
+ if (dp->hash == hash) {
+ // HASH match! And not too old!
+ if (dp->alen == addrlen &&
+ dp->plen == datalen &&
+ memcmp(addr, dp->addresses, addrlen) == 0 &&
+ memcmp(data, dp->packet, datalen) == 0) {
+ // PACKET MATCH!
+ dp->seen += 1;
+ return dp;
+ }
+ // no packet match.. check next
+ }
+ dpp = &dp->next;
+ }
+ // dpp points to pointer at the tail of the chain
+
+ // 4) Add comparison copy of non-dupe into dupe-db
+
+ dp = dupecheck_db_alloc(addrlen, datalen);
+ if (dp == NULL) return NULL; // alloc error!
+ *dpp = dp; // Put it on tail of existing chain
+
+
+ memcpy(dp->addresses, addr, addrlen);
+ memcpy(dp->packet, data, datalen);
+
+ dp->seen = 1; // First observation gets number 1
+ dp->hash = hash;
+ dp->t = tick.tv_sec;
+ dp->t_exp = tick.tv_sec + dpc->storetime;
+ return NULL;
+}
+
+
+/*
+ * dupecheck_pbuf() returns pointer to dupe record, if pbuf is
+ * a duplicate. Otherwise it return a NULL.
+ */
+dupe_record_t *dupecheck_pbuf(dupecheck_t *dpc, struct pbuf_t *pb, const int viscous_delay)
+{
+ int i;
+ uint32_t hash, idx;
+ dupe_record_t **dpp, *dp;
+ const char *addr = pb->data;
+ int alen = pb->dstcall_end - addr;
+
+ const char *dataend = pb->data + pb->packet_len;
+ const char *data = pb->info_start;
+ int dlen = dataend - data;
+
+ int addrlen = alen;
+ int datalen = dlen;
+ char *p;
+
+ /* if (debug && pb->is_aprs) {
+ printf("dupecheck[1] addr='");
+ fwrite(addr, alen, 1, stdout);
+ printf("' data='");
+ fwrite(data, dlen, 1, stdout);
+ printf("'\n");
+ } */
+
+
+ // Canonic tail has no SPACEs in data portion!
+ // TODO: how to treat 0 bytes ???
+
+ if (!pb->is_aprs) {
+ // data and dlen are raw AX.25 section pointers
+ data = (const char*) pb->ax25data;
+ datalen = pb->ax25datalen;
+
+ } else { // Do with APRS rules
+ for (;;) {
+
+ // 1) collect canonic rep of the address
+ i = 1;
+ for (addrlen = 0; addrlen < alen; ++ addrlen) {
+ const char c = addr[addrlen];
+ if (c == 0 || c == ',' || c == ':') {
+ break;
+ }
+ if (c == '-' && i) {
+ i = 0;
+ }
+ }
+ while (datalen > 0 && data[datalen-1] == ' ')
+ --datalen;
+
+ if (data[0] == '}') {
+ // 3rd party frame!
+ addr = data+1;
+ p = memchr(addr,':',datalen-1);
+ if (p == NULL)
+ break; // Invalid 3rd party frame, no ":" in it
+ alen = p - addr;
+ data = p+1;
+ datalen = dataend - data;
+
+ /* if (debug && pb->is_aprs) {
+ printf("dupecheck[2] 3rd-party: addr='");
+ fwrite(addr, alen, 1, stdout);
+ printf("' data='");
+ fwrite(data, datalen, 1, stdout);
+ printf("'\n");
+ } */
+
+ continue; // repeat the processing!
+ }
+ break; // No repeat necessary in general case
+ }
+ }
+
+ // 2) calculate checksum (from disjoint memory areas)
+
+ /* if (debug && pb->is_aprs) {
+ printf("dupecheck[3] addr='");
+ fwrite(addr, addrlen, 1, stdout);
+ printf("' data='");
+ fwrite(data, datalen, 1, stdout);
+ printf("'\n");
+ } */
+
+ hash = keyhash(addr, addrlen, 0);
+ hash = keyhash(data, datalen, hash);
+ idx = hash;
+
+ /* if (debug>1) {
+ printf("DUPECHECK: Addr='");
+ fwrite(addr, 1, addrlen, stdout);
+ printf("' Data='");
+ fwrite(data, 1, datalen, stdout);
+ printf("' hash=%x\n", hash);
+ }
+ */
+
+ // 3) lookup if same checksum is in some hash bucket chain
+ // 3b) compare packet...
+ // 3b1) flag as F_DUPE if so
+ // DUPECHECK_DB_SIZE == 16 -> 4 bits index
+ idx ^= (idx >> 16); /* fold the hash bits.. */
+ idx ^= (idx >> 8); /* fold the hash bits.. */
+ idx ^= (idx >> 4); /* fold the hash bits.. */
+ i = idx % DUPECHECK_DB_SIZE;
+ dpp = &(dpc->dupecheck_db[i]);
+ while (*dpp) {
+ dp = *dpp;
+ if ((dp->t_exp - tick.tv_sec) < 0) {
+ // Old ones are discarded when seen
+ *dpp = dp->next;
+ dp->next = NULL;
+ dupecheck_put(dp);
+ continue;
+ }
+ if (dp->hash == hash) {
+ // HASH match! And not too old!
+ if (dp->alen == addrlen &&
+ dp->plen == datalen &&
+ memcmp(addr, dp->addresses, addrlen) == 0 &&
+ memcmp(data, dp->packet, datalen) == 0) {
+ // PACKET MATCH!
+ if (viscous_delay > 0)
+ dp->delayed_seen += 1;
+ else
+ dp->seen += 1;
+ return dp;
+ }
+ // no packet match.. check next
+ }
+ dpp = &dp->next;
+ }
+ // dpp points to pointer at the tail of the chain
+
+ // 4) Add comparison copy of non-dupe into dupe-db
+
+ dp = dupecheck_db_alloc(addrlen, datalen);
+ if (dp == NULL) {
+ if (debug) printf("DUPECHECK ALLOC ERROR!\n");
+ return NULL; // alloc error!
+ }
+ *dpp = dp; // Put it on tail of existing chain
+
+ memcpy(dp->addresses, addr, addrlen);
+ memcpy(dp->packet, data, datalen);
+
+ dp->pbuf = pbuf_get(pb); // increments refcount
+ if (viscous_delay > 0) { // First observation gets number 1
+ dp->seen = 0;
+ dp->delayed_seen = 1;
+ dp->pbuf = pb;
+ } else {
+ dp->seen = 1;
+ dp->delayed_seen = 0;
+ }
+
+ dp->hash = hash;
+ dp->t = tick.tv_sec;
+ dp->t_exp = tick.tv_sec + dpc->storetime;
+
+ return dp;
+}
+
+/*
+ * dupechecker aprx poll integration, timed tasks control
+ *
+ */
+
+static struct timeval dupecheck_cleanup_nexttime;
+
+static void dupecheck_resettime(void *arg)
+{
+ struct timeval *tv = (struct timeval *)arg;
+ *tv = tick;
+}
+
+int dupecheck_prepoll(struct aprxpolls *app)
+{
+ if (time_reset) {
+ dupecheck_resettime(&dupecheck_cleanup_nexttime);
+ }
+
+ if (dupecheck_cleanup_nexttime.tv_sec == 0) dupecheck_cleanup_nexttime = tick;
+
+ if (tv_timercmp(&dupecheck_cleanup_nexttime, &app->next_timeout) > 0)
+ app->next_timeout = dupecheck_cleanup_nexttime;
+
+ return 0; /* No poll descriptors, only time.. */
+}
+
+
+int dupecheck_postpoll(struct aprxpolls *app)
+{
+ if (tv_timercmp(&dupecheck_cleanup_nexttime, &tick) > 0)
+ return 0; /* Too early.. */
+
+ tv_timeradd_seconds( &dupecheck_cleanup_nexttime, &tick, 30 ); // tick every 30 second or so
+
+ dupecheck_cleanup();
+
+ return 0;
+}
diff --git a/erlang.c b/erlang.c
new file mode 100644
index 0000000..11f07bc
--- /dev/null
+++ b/erlang.c
@@ -0,0 +1,703 @@
+/* **************************************************************** *
+ * *
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ * **************************************************************** */
+
+
+#include "aprx.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+
+
+/* The erlang module accounts data reception per 1m/10m/60m
+ intervals, and reports them on verbout.. */
+
+
+/* #define USE_ONE_MINUTE_INTERVAL 1 */
+
+
+static struct timeval erlang_time_end_1min;
+static float erlang_time_ival_1min = 1.0;
+
+static struct timeval erlang_time_end_10min;
+static float erlang_time_ival_10min = 1.0;
+
+#ifdef ERLANGSTORAGE
+static struct timeval erlang_time_end_60min;
+static float erlang_time_ival_60min = 1.0;
+#endif
+
+#ifdef ERLANGSTORAGE
+static const char *erlangtitle = "APRX SNMP + Erlang dataset\n";
+#endif
+
+int erlangsyslog; /* if set, will log via syslog(3) */
+int erlanglog1min; /* if set, will log also "ERLANG1" interval */
+
+const char *erlanglogfile;
+const char *erlang_backingstore = VARRUN "/aprx.state";
+
+#ifdef ERLANGSTORAGE
+static int erlang_file_fd = -1;
+static int erlang_mmap_size;
+#endif
+
+static void *erlang_mmap;
+
+struct erlanghead *ErlangHead;
+struct erlangline **ErlangLines;
+int ErlangLinesCount;
+int erlang_data_is_nonshared; /* In embedded target.. */
+
+struct erlang_file {
+ struct erlanghead head;
+ struct erlangline lines[1];
+};
+
+static void erlang_backingstore_startops(void)
+{
+ ErlangHead->server_pid = getpid();
+ ErlangHead->start_time = time(NULL);
+
+ if (!mycall)
+ strncpy(ErlangHead->mycall, "N0CALL",
+ sizeof(ErlangHead->mycall));
+ else
+ strncpy(ErlangHead->mycall, mycall,
+ sizeof(ErlangHead->mycall));
+ ErlangHead->mycall[sizeof(ErlangHead->mycall) - 1] = 0; /* NUL terminate */
+}
+
+static int erlang_backingstore_grow(int do_create, int add_count)
+{
+ struct erlang_file *EF;
+ int i;
+#ifdef ERLANGSTORAGE
+ struct stat st;
+ char buf[256];
+ int new_size, pagesize = sysconf(_SC_PAGE_SIZE);
+ int doing_init = 0;
+
+ if (erlang_data_is_nonshared)
+ goto embedded_only;
+
+ if (erlang_file_fd < 0) {
+ goto embedded_only;
+ }
+
+ fstat(erlang_file_fd, &st);
+ lseek(erlang_file_fd, 0, SEEK_END);
+
+ new_size = st.st_size;
+
+ if (new_size % pagesize) {
+ new_size /= pagesize;
+ ++new_size;
+ new_size *= pagesize;
+ }
+ if (new_size == 0) {
+ new_size = pagesize;
+ doing_init = 1;
+ }
+ /* new_size expanded to be exact page size multiple. */
+
+ /* If the new size is larger than the file size..
+ .. and at least one page size (e.g. 4 kB) .. */
+
+ if (new_size > st.st_size) {
+ /* .. then we fill in the file to given size. */
+ int i, rc, l;
+ i = st.st_size;
+ memset(buf, 0, sizeof(buf));
+ lseek(erlang_file_fd, 0, SEEK_END);
+ while (i < new_size) {
+ l = sizeof(buf);
+ if (new_size - i < l)
+ l = new_size - i;
+ rc = write(erlang_file_fd, buf, l);
+ if (rc < 0 && errno == EINTR)
+ continue;
+ if (rc != l)
+ break;
+ i += rc;
+ }
+ }
+
+ redo_open:;
+
+ if (erlang_mmap) {
+ msync(erlang_mmap, erlang_mmap_size, MS_SYNC);
+ munmap(erlang_mmap, erlang_mmap_size);
+ erlang_mmap = NULL;
+ erlang_mmap_size = 0;
+ ErlangHead = NULL;
+ }
+
+ /* Some (early Linux) systems mmap() offset on IO pointer... */
+ lseek(erlang_file_fd, 0, SEEK_SET);
+ fstat(erlang_file_fd, &st);
+
+ erlang_mmap_size = st.st_size;
+ erlang_mmap =
+ mmap(NULL, erlang_mmap_size,
+ PROT_READ | (do_create ? PROT_WRITE : 0), MAP_SHARED,
+ erlang_file_fd, 0);
+ if (erlang_mmap == MAP_FAILED) {
+ erlang_mmap = NULL;
+ syslog(LOG_ERR,
+ "Erlang-file mmap() failed, fd=%d, errno=%d: %s",
+ erlang_file_fd, errno, strerror(errno));
+ }
+ if (erlang_mmap) {
+
+ int rc, l;
+ EF = erlang_mmap;
+
+ ErlangHead = &EF->head;
+
+ if (EF->head.version != ERLANGLINE_STRUCT_VERSION ||
+ EF->head.last_update == 0) {
+ if (doing_init) {
+ /* Not initialized ? */
+ memset(erlang_mmap, 0, erlang_mmap_size);
+
+ strcpy(EF->head.title, erlangtitle);
+ EF->head.version =
+ ERLANGLINE_STRUCT_VERSION;
+ EF->head.linecount = 0;
+ EF->head.last_update = tick.tv_sec;
+ ErlangLinesCount = 0;
+ } else {
+ /* Wrong head magic, and not doing block init.. */
+ munmap(erlang_mmap, erlang_mmap_size);
+ erlang_mmap = NULL;
+ erlang_mmap_size = 0;
+ syslog(LOG_ERR,
+ "Erlang-file has bad magic in it, not opening! Not modifying!");
+ close(erlang_file_fd);
+ erlang_file_fd = -1;
+
+ goto embedded_only; /* BAD BAD ! */
+ }
+ }
+
+ if (EF->head.linecount != ErlangLinesCount
+ || add_count > 0) {
+ /* must resize.. */
+ int new_count = EF->head.linecount + add_count;
+ new_size =
+ sizeof(struct erlang_file) +
+ sizeof(struct erlangline) * (new_count -
+ 1);
+
+ if (new_size % pagesize) {
+ new_size /= pagesize;
+ ++new_size;
+ new_size *= pagesize;
+ }
+
+ i = st.st_size;
+ memset(buf, 0, sizeof(buf));
+ lseek(erlang_file_fd, 0, SEEK_END); /* append on the file.. */
+ while (i < new_size) {
+ l = sizeof(buf);
+ if (new_size - i < l)
+ l = new_size - i;
+ rc = write(erlang_file_fd, buf, l);
+ if (rc < 0 && errno == EINTR)
+ continue;
+ if (rc != l)
+ break;
+ i += rc;
+ }
+
+ if (i < new_size) {
+ munmap(erlang_mmap, erlang_mmap_size);
+ erlang_mmap = NULL;
+
+ goto embedded_only; /* BAD BAD ! */
+ }
+
+ add_count = 0;
+ if (do_create)
+ EF->head.linecount = new_count;
+ ErlangLinesCount = new_count;
+
+ goto redo_open; /* redo mapping */
+ }
+
+ /* Ok, successfull open, correct linecount */
+ ErlangLines =
+ (void *) realloc((void *) ErlangLines,
+ (ErlangLinesCount +
+ 1) * sizeof(void *));
+
+ for (i = 0; i < ErlangLinesCount; ++i) {
+ ErlangLines[i] = &EF->lines[i];
+ }
+
+
+
+ return 0; /* OK ! */
+ }
+
+ embedded_only:;
+#endif /* ... ERLANGSTORAGE ... */
+
+ erlang_data_is_nonshared = 1;
+
+ if (add_count > 0 || !erlang_mmap) {
+ ErlangLinesCount += add_count;
+ erlang_mmap = realloc(erlang_mmap, sizeof(*EF) +
+ (ErlangLinesCount +
+ 1) * sizeof(struct erlangline));
+ }
+
+ EF = erlang_mmap;
+ ErlangHead = &EF->head;
+
+ /* Ok, successfull open, correct linecount */
+ ErlangLines =
+ (void *) realloc((void *) ErlangLines,
+ (ErlangLinesCount + 1) * sizeof(void *));
+
+ for (i = 0; i < ErlangLinesCount; ++i) {
+ ErlangLines[i] = &EF->lines[i];
+ }
+
+ return 0;
+}
+
+static int erlang_backingstore_open(int do_create)
+{
+#ifdef ERLANGSTORAGE
+ if (!erlang_backingstore) {
+ syslog(LOG_ERR, "erlang_backingstore not defined!");
+ erlang_data_is_nonshared = 1;
+ }
+ if (erlang_file_fd < 0 && erlang_backingstore) {
+ erlang_file_fd = open(erlang_backingstore, do_create ? O_RDWR : O_RDONLY, 0644); /* Presume: it exists! */
+ if ((erlang_file_fd < 0) && do_create && (errno == ENOENT)) {
+ erlang_file_fd =
+ open(erlang_backingstore,
+ O_RDWR | O_CREAT | O_EXCL, 0644);
+ }
+ }
+ if (erlang_file_fd < 0) {
+ syslog(LOG_ERR,
+ "Open of '%s' for erlang_backingstore file failed! errno=%d: %s",
+ erlang_backingstore, errno, strerror(errno));
+ erlang_data_is_nonshared = 1;
+ }
+#endif
+ return erlang_backingstore_grow(do_create, 0); /* Just open */
+}
+
+
+static struct erlangline *erlang_findline(const char *portname,
+ int bytes_per_minute)
+{
+ int i;
+ struct erlangline *E;
+ if (portname == NULL) return NULL;
+
+ if (bytes_per_minute == 0)
+ bytes_per_minute = (int) ((1200.0 * 60) / 8.2); // Default of 1200 bps
+
+ /* Allocate a new ErlangLines[] entry for this object,
+ if no existing one is found.. */
+
+ E = NULL;
+ if (ErlangLines) {
+ for (i = 0; i < ErlangLinesCount; ++i) {
+ if (strcmp(portname, ErlangLines[i]->name) == 0) {
+ /* HOO-RAY! It is this one! */
+ E = ErlangLines[i];
+ break;
+ }
+ }
+ }
+ /* If found -- err... why we are SETing it AGAIN ? */
+
+
+ if (!E) {
+
+ /* Allocate a new one */
+ erlang_backingstore_grow(1, 1);
+ if (!ErlangLines)
+ return NULL; /* D'uh! */
+
+ E = ErlangLines[ErlangLinesCount - 1]; /* Last one is the lattest.. */
+
+ memset(E, 0, sizeof(*E));
+ strncpy(E->name, portname, sizeof(E->name) - 1);
+ E->name[sizeof(E->name) - 1] = 0;
+
+ E->erlang_capa = bytes_per_minute;
+ E->index = ErlangLinesCount - 1;
+
+#ifdef ERLANGSTORAGE
+ E->e1_cursor = 0;
+ E->e1_max = APRXERL_1M_COUNT;
+ E->e10_cursor = 0;
+ E->e10_max = APRXERL_10M_COUNT;
+ E->e60_cursor = 0;
+ E->e60_max = APRXERL_60M_COUNT;
+#else
+#if (USE_ONE_MINUTE_DATA == 1)
+ E->e1_cursor = 0;
+ E->e1_max = APRXERL_1M_COUNT;
+#else
+ E->e10_cursor = 0;
+ E->e10_max = APRXERL_10M_COUNT;
+#endif
+#endif
+ }
+ return E;
+}
+
+
+static void erlang_timer_init(void *dummy)
+{
+
+ /* Time intervals will end at next even
+ 1 minute/10 minutes/60 minutes,
+ although said interval will be shorter than full. */
+
+ erlang_time_end_1min.tv_sec = tick.tv_sec + 60 - (tick.tv_sec % 60);
+ erlang_time_end_1min.tv_usec = 0;
+ erlang_time_ival_1min = (float) (60 - tick.tv_sec % 60) / 60.0;
+
+ erlang_time_end_10min.tv_sec = tick.tv_sec + 600 - (tick.tv_sec % 600);
+ erlang_time_end_10min.tv_usec = 0;
+ erlang_time_ival_10min = (float) (600 - tick.tv_sec % 600) / 600.0;
+
+#ifdef ERLANGSTORAGE
+ erlang_time_end_60min.tv_sec = tick.tv_sec + 3600 - (tick.tv_sec % 3600);
+ erlang_time_end_60min.tv_usec = 0;
+ erlang_time_ival_60min = (float) (3600 - tick.tv_sec % 3600) / 3600.0;
+#endif
+}
+
+
+/*
+ * erlang_set()
+ */
+void erlang_set(const char *portname, int bytes_per_minute)
+{
+ erlang_findline(portname, bytes_per_minute);
+}
+
+/*
+ * erlang_add()
+ */
+void erlang_add(const char *portname, ErlangMode erl, int bytes, int packets)
+{
+ struct erlangline *E;
+ if (!portname) return;
+
+ E = erlang_findline(portname, (int) ((1200.0 * 60) / 8.2));
+
+ if (debug > 1)
+ printf("erlang_add(%s, %s, %d, %d)\n", portname,
+ (erl == ERLANG_RX ? "RX":(erl == ERLANG_TX ? "TX": "DROP")),
+ bytes, packets);
+
+ if (!E)
+ return;
+
+ if (erl == ERLANG_RX) {
+ E->SNMP.bytes_rx += bytes;
+ E->SNMP.packets_rx += packets;
+ E->SNMP.update = tick.tv_sec;
+ E->last_update = tick.tv_sec;
+
+#ifdef ERLANGSTORAGE
+ E->erl1m.bytes_rx += bytes;
+ E->erl1m.packets_rx += packets;
+ E->erl1m.update = tick.tv_sec;
+
+ E->erl10m.bytes_rx += bytes;
+ E->erl10m.packets_rx += packets;
+ E->erl10m.update = tick.tv_sec;
+
+ E->erl60m.bytes_rx += bytes;
+ E->erl60m.packets_rx += packets;
+ E->erl60m.update = tick.tv_sec;
+#else
+#if (USE_ONE_MINUTE_STORAGE == 1)
+ E->erl1m.bytes_rx += bytes;
+ E->erl1m.packets_rx += packets;
+ E->erl1m.update = tick.tv_sec;
+#else
+ E->erl10m.bytes_rx += bytes;
+ E->erl10m.packets_rx += packets;
+ E->erl10m.update = tick.tv_sec;
+#endif
+#endif
+ }
+ if (erl == ERLANG_TX) {
+ E->SNMP.bytes_tx += bytes;
+ E->SNMP.packets_tx += packets;
+ E->SNMP.update = tick.tv_sec;
+ E->last_update = tick.tv_sec;
+
+#ifdef ERLANGSTORAGE
+ E->erl1m.bytes_tx += bytes;
+ E->erl1m.packets_tx += packets;
+ E->erl1m.update = tick.tv_sec;
+
+ E->erl10m.bytes_tx += bytes;
+ E->erl10m.packets_tx += packets;
+ E->erl10m.update = tick.tv_sec;
+
+ E->erl60m.bytes_tx += bytes;
+ E->erl60m.packets_tx += packets;
+ E->erl60m.update = tick.tv_sec;
+#else
+#if (USE_ONE_MINUTE_STORAGE == 1)
+ E->erl1m.bytes_tx += bytes;
+ E->erl1m.packets_tx += packets;
+ E->erl1m.update = tick.tv_sec;
+#else
+ E->erl10m.bytes_tx += bytes;
+ E->erl10m.packets_tx += packets;
+ E->erl10m.update = tick.tv_sec;
+#endif
+#endif
+ }
+ if (erl == ERLANG_DROP) {
+ E->SNMP.bytes_rxdrop += bytes;
+ E->SNMP.packets_rxdrop += packets;
+ E->SNMP.update = tick.tv_sec;
+ E->last_update = tick.tv_sec;
+
+#ifdef ERLANGSTORAGE
+ E->erl1m.bytes_rxdrop += bytes;
+ E->erl1m.packets_rxdrop += packets;
+ E->erl1m.update = tick.tv_sec;
+
+ E->erl10m.bytes_rxdrop += bytes;
+ E->erl10m.packets_rxdrop += packets;
+ E->erl10m.update = tick.tv_sec;
+
+ E->erl60m.bytes_rxdrop += bytes;
+ E->erl60m.packets_rxdrop += packets;
+ E->erl60m.update = tick.tv_sec;
+#else
+#if (USE_ONE_MINUTE_STORAGE == 1)
+ E->erl1m.bytes_rxdrop += bytes;
+ E->erl1m.packets_rxdrop += packets;
+ E->erl1m.update = tick.tv_sec;
+#else
+ E->erl10m.bytes_rxdrop += bytes;
+ E->erl10m.packets_rxdrop += packets;
+ E->erl10m.update = tick.tv_sec;
+#endif
+#endif
+ }
+}
+
+
+/*
+ * erlang_time_end() - process erlang measurement interval time end event
+ */
+static void erlang_time_end(void)
+{
+ int i;
+ char msgbuf[500];
+ char logtime[40];
+ FILE *fp = NULL;
+
+ if (erlanglogfile) {
+ /* actually we want it to the erlanglogfile... */
+ fp = fopen(erlanglogfile, "a");
+ }
+
+ printtime(logtime, sizeof(logtime));
+
+ if (tv_timercmp(&tick, &erlang_time_end_1min) >= 0) {
+ erlang_time_end_1min.tv_sec += 60;
+#if (defined(ERLANGSTORAGE) || (USE_ONE_MINUTE_STORAGE == 1))
+ for (i = 0; i < ErlangLinesCount; ++i) {
+ struct erlangline *E = ErlangLines[i];
+ E->last_update = tick.tv_sec;
+
+ if (erlanglog1min) {
+ sprintf(msgbuf,
+ "ERLANG%-2d %s Rx %6ld %3ld Dp %6ld %3ld Tx %6ld %3ld : %5.3f %5.3f %5.3f",
+ 1, E->name,
+ E->erl1m.bytes_rx,
+ E->erl1m.packets_rx,
+ E->erl1m.bytes_rxdrop,
+ E->erl1m.packets_rxdrop,
+ E->erl1m.bytes_tx, E->erl1m.packets_tx,
+ ((float) E->erl1m.bytes_rx /
+ (float) E->erlang_capa *
+ erlang_time_ival_1min),
+ ((float) E->erl1m.bytes_rxdrop /
+ (float) E->erlang_capa *
+ erlang_time_ival_1min),
+ ((float)E->erl1m.bytes_tx /
+ (float)E->erlang_capa *
+ erlang_time_ival_1min)
+ );
+ if (fp)
+ fprintf(fp, "%s %s\n", logtime,
+ msgbuf);
+ else if (erlangout)
+ printf("%ld\t%s\n", tick.tv_sec, msgbuf);
+ if (erlangsyslog)
+ syslog(LOG_INFO, "%ld %s", tick.tv_sec,
+ msgbuf);
+ }
+
+ E->erl1m.update = tick.tv_sec;
+ E->e1[E->e1_cursor] = E->erl1m;
+ ++E->e1_cursor;
+ if (E->e1_cursor >= E->e1_max)
+ E->e1_cursor = 0;
+
+ memset(&E->erl1m, 0, sizeof(E->erl1m));
+ E->erl1m.update = tick.tv_sec;
+ }
+ erlang_time_ival_1min = 1.0;
+#endif
+ }
+ if (tv_timercmp(&tick, &erlang_time_end_10min) >= 0) {
+ erlang_time_end_10min.tv_sec += 600;
+#if (defined(ERLANGSTORAGE) || (USE_ONE_MINUTE_STORAGE == 0))
+ for (i = 0; i < ErlangLinesCount; ++i) {
+ struct erlangline *E = ErlangLines[i];
+ E->last_update = tick.tv_sec;
+ sprintf(msgbuf,
+ "ERLANG%-2d %s Rx %6ld %3ld Dp %6ld %3ld Tx %6ld %3ld : %5.3f %5.3f %5.3f",
+ 10, E->name,
+ E->erl10m.bytes_rx, E->erl10m.packets_rx,
+ E->erl10m.bytes_rxdrop,
+ E->erl10m.packets_rxdrop,
+ E->erl10m.bytes_tx, E->erl10m.packets_tx,
+ ((float) E->erl10m.bytes_rx /
+ ((float) E->erlang_capa * 10.0 *
+ erlang_time_ival_10min)),
+ ((float) E->erl10m.bytes_rxdrop /
+ ((float) E->erlang_capa * 10.0 *
+ erlang_time_ival_10min)),
+ ((float)E->erl10m.bytes_tx /
+ ((float)E->erlang_capa * 10.0 *
+ erlang_time_ival_10min))
+ );
+ if (fp)
+ fprintf(fp, "%s %s\n", logtime, msgbuf);
+ else if (erlangout)
+ printf("%ld\t%s\n", tick.tv_sec, msgbuf);
+ if (erlangsyslog)
+ syslog(LOG_INFO, "%ld %s", tick.tv_sec, msgbuf);
+
+ E->erl10m.update = tick.tv_sec;
+ E->e10[E->e10_cursor] = E->erl10m;
+ ++E->e10_cursor;
+ if (E->e10_cursor >= E->e10_max)
+ E->e10_cursor = 0;
+ memset(&E->erl10m, 0, sizeof(E->erl10m));
+ E->erl10m.update = tick.tv_sec;
+ }
+ erlang_time_ival_10min = 1.0;
+#endif
+ }
+#ifdef ERLANGSTORAGE
+ if (tv_timercmp(&tick, &erlang_time_end_60min) >= 0) {
+ erlang_time_end_60min.tv_sec += 3600;
+ for (i = 0; i < ErlangLinesCount; ++i) {
+ struct erlangline *E = ErlangLines[i];
+ /* E->last_update = now.tv_sec; -- the 10 minute step does also this */
+ sprintf(msgbuf,
+ "ERLANG%-2d %s Rx %6ld %3ld Dp %6ld %3ld Tx %6ld %3ld : %5.3f %5.3f %5.3f",
+ 60, E->name,
+ E->erl60m.bytes_rx, E->erl60m.packets_rx,
+ E->erl60m.bytes_rxdrop,
+ E->erl60m.packets_rxdrop,
+ E->erl60m.bytes_tx, E->erl60m.packets_tx,
+ ((float) E->erl60m.bytes_rx /
+ ((float) E->erlang_capa * 60.0 *
+ erlang_time_ival_60min)),
+ ((float) E->erl60m.bytes_rxdrop /
+ ((float) E->erlang_capa * 60.0 *
+ erlang_time_ival_60min)),
+ ((float)E->erl60m.bytes_tx /
+ ((float)E->erlang_capa * 60.0 *
+ erlang_time_ival_60min))
+ );
+ if (fp)
+ fprintf(fp, "%s %s\n", logtime, msgbuf);
+ else if (erlangout)
+ printf("%ld\t%s\n", tick.tv_sec, msgbuf);
+ if (erlangsyslog)
+ syslog(LOG_INFO, "%ld %s", tick.tv_sec, msgbuf);
+
+ E->erl60m.update = tick.tv_sec;
+ E->e60[E->e60_cursor] = E->erl60m;
+ ++E->e60_cursor;
+ if (E->e60_cursor >= E->e60_max)
+ E->e60_cursor = 0;
+
+ memset(&E->erl60m, 0, sizeof(E->erl60m));
+ E->erl60m.update = tick.tv_sec;
+ }
+ erlang_time_ival_60min = 1.0;
+ }
+#endif
+ if (fp)
+ fclose(fp);
+}
+
+int erlang_prepoll(struct aprxpolls *app)
+{
+ if (time_reset) {
+ if (debug) printf("erlang_timer_init() to be called\n");
+ erlang_timer_init(NULL);
+ }
+
+ if (tv_timercmp(&app->next_timeout, &erlang_time_end_1min) > 0)
+ app->next_timeout = erlang_time_end_1min;
+ if (tv_timercmp(&app->next_timeout, &erlang_time_end_10min) > 0)
+ app->next_timeout = erlang_time_end_10min;
+#ifdef ERLANGSTORAGE
+ if (tv_timercmp(&app->next_timeout, &erlang_time_end_60min) > 0)
+ app->next_timeout = erlang_time_end_60min;
+#endif
+ return 0;
+}
+
+int erlang_postpoll(struct aprxpolls *app)
+{
+ if (tv_timercmp(&tick, &erlang_time_end_1min) >= 0 ||
+ tv_timercmp(&tick, &erlang_time_end_10min) >= 0
+#ifdef ERLANGSTORAGE
+ || tv_timercmp(&tick, &erlang_time_end_60min) >= 0
+#endif
+ )
+ erlang_time_end();
+
+ return 0;
+}
+
+
+void erlang_init(const char *syslog_facility_name)
+{
+ erlang_timer_init(NULL);
+}
+
+void erlang_start(int do_create)
+{
+ erlang_backingstore_open(do_create);
+ if (do_create > 1)
+ erlang_backingstore_startops();
+}
diff --git a/filter.c b/filter.c
new file mode 100644
index 0000000..df21556
--- /dev/null
+++ b/filter.c
@@ -0,0 +1,2331 @@
+/********************************************************************
+ * APRX -- 2nd generation APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ ********************************************************************/
+/*
+ * aprsc
+ *
+ * (c) Matti Aarnio, OH2MQK, <oh2mqk at sral.fi>
+ *
+ * This program is licensed under the BSD license, which can be found
+ * in the file LICENSE.
+ *
+ */
+
+#include "aprx.h"
+
+#include "cellmalloc.h"
+#include "historydb.h"
+#include "keyhash.h"
+
+/*
+ See: http://www.aprs-is.net/javaprssrvr/javaprsfilter.htm
+
+ a/latN/lonW/latS/lonE Area filter
+ b/call1/call2... Budlist filter (*)
+ d/digi1/digi2... Digipeater filter (*)
+ e/call1/call1/... Entry station filter (*)
+ f/call/dist Friend Range filter
+ g/call1/call2.. Group Messaging filter (*)
+ m/dist My Range filter
+ o/obj1/obj2... Object filter (*)
+ p/aa/bb/cc... Prefix filter
+ q/con/ana q Contruct filter
+ r/lat/lon/dist Range filter
+ s/pri/alt/over Symbol filter
+ t/poimntqsu3*c Type filter
+ t/poimntqsu3*c/call/km Type filter
+ u/unproto1/unproto2/.. Unproto filter (*)
+
+ Sample usage frequencies (out of entire APRS-IS):
+
+ 23.7 a/ <-- Optimize!
+ 9.2 b/ <-- Optimize?
+ 1.4 d/
+ 0.2 e/
+ 2.2 f/
+ 20.9 m/ <-- Optimize!
+ 0.2 o/
+ 14.4 p/ <-- Optimize!
+ 0.0 pk
+ 0.0 pm
+ 0.4 q/
+ 19.0 r/ <-- Optimize!
+ 0.1 s_
+ 1.6 s/
+ 6.6 t/
+ 0.1 u/
+
+
+ (*) = wild-card supported
+
+ Undocumented at above web-page, but apparent behaviour is:
+
+ - Everything not explicitely stated to be case sensitive is
+ case INSENSITIVE
+
+ - Minus-prefixes on filters behave as is there are two sets of
+ filters:
+
+ - filters without minus-prefixes add on approved set, and all
+ those without are evaluated at first
+ - filters with minus-prefixes are evaluated afterwards to drop
+ selections after the additive filter has been evaluated
+
+
+ - Our current behaviour is: "evaluate everything in entry order,
+ stop at first match", which enables filters like:
+ p/OH2R -p/OH2 p/OH
+ while javAPRSSrvr filter adjunct behaves like the request is:
+ -p/OH2 p/OH
+ that is, OH2R** stations are not passed thru.
+
+*/
+
+
+/* FIXME: What exactly is the meaning of negation on the pattern ?
+** Match as a failure to match, and stop searching ?
+** Something filter dependent ?
+**
+** javAPRSSrvr Filter Adjunct manual tells:
+
+ #14 Exclusion filter
+
+All the above filters also support exclusion. Be prefixing the above filters with a
+dash the result will be the opposite. Any packet that match the exclusion filter will
+NOT pass. The exclusion filters will be processed first so if there is a match for an
+exclusion then the packet is not passed no matter any other filter definitions.
+
+*/
+
+#define WildCard 0x80 /* it is wild-carded prefix string */
+#define NegationFlag 0x40 /* */
+#define LengthMask 0x0F /* only low 4 bits encode length */
+
+/* values above are chosen for 4 byte alignment.. */
+
+struct filter_refcallsign_t {
+ char callsign[CALLSIGNLEN_MAX+1]; /* size: 10.. */
+ int8_t reflen; /* length and flags */
+};
+struct filter_head_t {
+ struct filter_t *next;
+ const char *text; /* filter text as is */
+ float f_latN, f_lonE;
+ union {
+ float f_latS; /* for A filter */
+ float f_coslat; /* for R filter */
+ } u1;
+ union {
+ float f_lonW; /* for A filter */
+ float f_dist; /* for R filter */
+ } u2;
+ time_t hist_age;
+
+ char type; /* 1 char */
+ int16_t negation; /* boolean flag */
+ union {
+ int16_t numnames; /* used as named, and as cache validity flag */
+ int16_t len1s; /* or len1 of s-filter */
+ } u3;
+ union {
+ int16_t bitflags; /* used as bit-set on T_*** enumerations */
+ int16_t len1; /* or as len2 of s-filter */
+ } u4;
+ union {
+ struct { int16_t len2s, len2, len3s, len3; } lens; /* of s-filter */
+ /* for cases where there is only one.. */
+ struct filter_refcallsign_t refcallsign;
+ /* malloc()ed array, alignment important! */
+ struct filter_refcallsign_t *refcallsigns;
+ } u5;
+};
+
+struct filter_t {
+ struct filter_head_t h;
+#define FILT_TEXTBUFSIZE (508-sizeof(struct filter_head_t))
+ char textbuf[FILT_TEXTBUFSIZE];
+};
+
+#define QC_C 0x001 /* Q-filter flag bits */
+#define QC_X 0x002
+#define QC_U 0x004
+#define QC_o 0x008
+#define QC_O 0x010
+#define QC_S 0x020
+#define QC_r 0x040
+#define QC_R 0x080
+#define QC_Z 0x100
+#define QC_I 0x200
+
+#define QC_AnalyticsI 0x800
+
+/*
+// For q-filter analytics: entrycall igate filter database
+struct filter_entrycall_t {
+ struct filter_entrycall_t *next;
+ time_t expirytime;
+ uint32_t hash;
+ int len;
+ char callsign[CALLSIGNLEN_MAX+1];
+};
+*/
+/*
+struct filter_wx_t {
+ struct filter_wx_t *next;
+ time_t expirytime;
+ uint32_t hash;
+ int len;
+ char callsign[CALLSIGNLEN_MAX+1];
+};
+*/
+
+typedef enum {
+ MatchExact,
+ MatchPrefix,
+ MatchWild
+} MatchEnum;
+
+/*
+#define FILTER_ENTRYCALL_HASHSIZE 2048 // Around 500-600 in db, this looks
+ // for collision free result..
+int filter_entrycall_maxage = 60*60; // 1 hour, default. Validity on
+ // lookups: 5 minutes less..
+int filter_entrycall_cellgauge;
+
+struct filter_entrycall_t *filter_entrycall_hash[FILTER_ENTRYCALL_HASHSIZE];
+*/
+
+/*
+#define FILTER_WX_HASHSIZE 1024 // Around 300-400 in db, this looks
+ // for collision free result..
+int filter_wx_maxage = 60*60; // 1 hour, default. Validity on
+ // lookups: 5 minutes less..
+int filter_wx_cellgauge;
+
+struct filter_wx_t *filter_wx_hash[FILTER_WX_HASHSIZE];
+*/
+
+#ifndef _FOR_VALGRIND_
+cellarena_t *filter_cells;
+//cellarena_t *filter_entrycall_cells;
+//cellarena_t *filter_wx_cells;
+#endif
+
+
+int hist_lookup_interval = 20; /* FIXME: Configurable: Cache historydb
+ position lookups this much seconds on
+ each filter entry referring to some
+ fixed callsign (f,m,t) */
+
+float filter_lat2rad(float lat)
+{
+ return (lat * (M_PI / 180.0));
+}
+
+float filter_lon2rad(float lon)
+{
+ return (lon * (M_PI / 180.0));
+}
+
+
+const int filter_cellsize = sizeof(struct filter_t);
+const int filter_cellalign = __alignof__(struct filter_t);
+
+void filter_init(void)
+{
+#ifndef _FOR_VALGRIND_
+ /* A _few_... */
+
+ filter_cells = cellinit( "filter",
+ filter_cellsize,
+ filter_cellalign,
+ CELLMALLOC_POLICY_LIFO,
+ 4 /* 4 kB at the time,
+ should be enough in all cases.. */,
+ 0 /* minfree */ );
+
+ /* printf("filter: sizeof=%d alignof=%d\n",
+ sizeof(struct filter_t),__alignof__(struct filter_t)); */
+
+/*
+ // Couple thousand
+ filter_entrycall_cells = cellinit( "entrycall",
+ sizeof(struct filter_entrycall_t),
+ __alignof__(struct filter_entrycall_t),
+ CELLMALLOC_POLICY_FIFO,
+ 32, // 32 kB at the time,
+ 0 // minfree
+ );
+*/
+/*
+ // Under 1 thousand..
+ filter_wx_cells = cellinit( "wxcalls",
+ sizeof(struct filter_wx_t),
+ __alignof__(struct filter_wx_t),
+ CELLMALLOC_POLICY_FIFO,
+ 32, // 32 kB at the time
+ 0 // minfree
+ );
+*/
+#endif
+}
+
+#if 0
+static void filter_entrycall_free(struct filter_entrycall_t *f)
+{
+#ifndef _FOR_VALGRIND_
+ cellfree( filter_entrycall_cells, f );
+#else
+ free(f);
+#endif
+ -- filter_entrycall_cellgauge;
+}
+
+/*
+ * filter_entrycall_insert() is for support of q//i filters.
+ * That is, "pass on any message that has traversed thru entry
+ * igate which has identified itself with qAr or qAR. Not all
+ * messages traversed thru such gate will have those same q-cons
+ * values, thus this database keeps info about entry igate that
+ * have shown such capability in recent past.
+ *
+ * This must be called by the incoming_parse() in every case
+ * (or at least when qcons is either 'r' or 'R'.)
+ *
+ * The key has no guaranteed alignment, no way to play tricks
+ * with gcc builtin optimizers.
+ */
+
+int filter_entrycall_insert(struct pbuf_t *pb)
+{
+ struct filter_entrycall_t *f, **fp, *f2;
+ /* OK, pre-parsing produced accepted result */
+ uint32_t hash;
+ int idx, keylen;
+ const char qcons = pb->qconst_start[2];
+ const char *key = pb->qconst_start+4;
+ char uckey[CALLSIGNLEN_MAX+1];
+
+ for (keylen = 0; keylen < CALLSIGNLEN_MAX; ++keylen) {
+ int c = key[keylen];
+ if (c == ',' || c == ':')
+ break;
+ if ('a' <= c && c <= 'z')
+ c -= ('a' - 'A');
+ uckey[keylen] = c;
+ uckey[keylen+1] = 0;
+ }
+ if ((key[keylen] != ',' && key[keylen] != ':') || keylen < CALLSIGNLEN_MIN)
+ return 0; /* Bad entry-station callsign */
+
+ pb->entrycall_len = keylen; // FIXME: should be in incoming parser...
+
+ /* We insert only those that have Q-Constructs of qAR or qAr */
+ if (qcons != 'r' && qcons != 'R') return 0;
+
+ hash = keyhash(uckey, keylen, 0);
+ idx = (hash ^ (hash >> 11) ^ (hash >> 22) ) % FILTER_ENTRYCALL_HASHSIZE; /* Fold the hashbits.. */
+
+
+ fp = &filter_entrycall_hash[idx];
+ f2 = NULL;
+ while (( f = *fp )) {
+ if ( f->hash == hash ) {
+ if (f->len == keylen) {
+ int cmp = strncasecmp(f->callsign, uckey, keylen);
+ if (cmp == 0) { /* Have key match */
+ f->expirytime = tick.tv_sec + filter_entrycall_maxage;
+ f2 = f;
+ break;
+ }
+ }
+ }
+ /* No match at all, advance the pointer.. */
+ fp = &(f -> next);
+ }
+ if (!f2) {
+
+ /* Allocate and insert into hash table */
+
+ fp = &filter_entrycall_hash[idx];
+
+#ifndef _FOR_VALGRIND_
+ f = cellmalloc(filter_entrycall_cells);
+#else
+ f = calloc(1, sizeof(*f));
+#endif
+ if (f) {
+ f->next = *fp;
+ f->expirytime = tick.tv_sec + filter_entrycall_maxage;
+ f->hash = hash;
+ f->len = keylen;
+ memcpy(f->callsign, uckey, keylen);
+ memset(f->callsign+keylen, 0, sizeof(f->callsign)-keylen);
+
+ *fp = f2 = f;
+
+ ++ filter_entrycall_cellgauge;
+ }
+ }
+
+ return (f2 != NULL);
+}
+
+/*
+ * filter_entrycall_lookup() is for support of q//i filters.
+ * That is, "pass on any message that has traversed thru entry
+ * igate which has identified itself with qAr or qAR. Not all
+ * messages traversed thru such gate will have those same q-cons
+ * values, thus this keeps database about entry servers that have
+ * shown such capability in recent past.
+ *
+ * The key has no guaranteed alignment, no way to play tricks
+ * with gcc builtin optimizers.
+ */
+
+static int filter_entrycall_lookup(const struct pbuf_t *pb)
+{
+ struct filter_entrycall_t *f, **fp, *f2;
+ const char *key = pb->qconst_start+4;
+ const int keylen = pb->entrycall_len;
+
+ uint32_t hash = keyhashuc(key, keylen, 0);
+ int idx = ( hash ^ (hash >> 11) ^ (hash >> 22) ) % FILTER_ENTRYCALL_HASHSIZE; /* fold the hashbits.. */
+
+ f2 = NULL;
+
+ fp = &filter_entrycall_hash[idx];
+ while (( f = *fp )) {
+ if ( f->hash == hash ) {
+ if (f->len == keylen) {
+ int rc = strncasecmp(f->callsign, key, keylen);
+ if (rc == 0) { /* Have key match, see if it is
+ still valid entry ? */
+ if ((f->expirytime - (tick.tv_sec - 60)) < 0) {
+ f2 = f;
+ break;
+ }
+ }
+ }
+ }
+ /* No match at all, advance the pointer.. */
+ fp = &(f -> next);
+ }
+
+ return (f2 != NULL);
+}
+
+/*
+ * The filter_entrycall_cleanup() does purge old entries
+ * out of the database. Run about once a minute.
+ */
+void filter_entrycall_cleanup(void)
+{
+ int k, cleancount = 0;
+ struct filter_entrycall_t *f, **fp;
+
+ for (k = 0; k < FILTER_ENTRYCALL_HASHSIZE; ++k) {
+ fp = & filter_entrycall_hash[k];
+ while (( f = *fp )) {
+ /* Did it expire ? */
+ if ((f->expirytime - tick.tv_sec) <= 0) {
+ *fp = f->next;
+ f->next = NULL;
+ filter_entrycall_free(f);
+ ++cleancount;
+ continue;
+ }
+ /* No purge, advance the pointer.. */
+ fp = &(f -> next);
+ }
+ }
+
+ // hlog( LOG_DEBUG, "filter_entrycall_cleanup() removed %d entries, count now: %ld",
+ // cleancount, filter_entrycall_cellgauge );
+}
+
+/*
+ * The filter_entrycall_atend() does purge all entries
+ * out of the database. Run at the exit of the program.
+ * This exists primarily to make valgrind happy...
+ */
+void filter_entrycall_atend(void)
+{
+ int k;
+ struct filter_entrycall_t *f, **fp;
+
+ for (k = 0; k < FILTER_ENTRYCALL_HASHSIZE; ++k) {
+ fp = & filter_entrycall_hash[k];
+ while (( f = *fp )) {
+ *fp = f->next;
+ f->next = NULL;
+ filter_entrycall_free(f);
+ }
+ }
+}
+
+
+void filter_entrycall_dump(FILE *fp)
+{
+ int k;
+ struct filter_entrycall_t *f;
+
+ for (k = 0; k < FILTER_ENTRYCALL_HASHSIZE; ++k) {
+ f = filter_entrycall_hash[k];
+
+ for ( ; f; f = f->next ) {
+ fprintf( fp, "%ld\t%s\n",
+ (long)f->expirytime, f->callsign );
+ }
+ }
+}
+#endif
+
+/* ================================================================ */
+
+
+#if 0
+static void filter_wx_free(struct filter_wx_t *f)
+{
+#ifndef _FOR_VALGRIND_
+ cellfree( filter_wx_cells, f );
+#else
+ free(f);
+#endif
+ --filter_wx_cellgauge;
+}
+
+/*
+ * The filter_wx_insert() does lookup key storage for problem of:
+ *
+ * Positionless T_WX packets want also position packets on output filters.
+ */
+
+int filter_wx_insert(struct pbuf_t *pb)
+{
+ struct filter_wx_t *f, **fp, *f2;
+ /* OK, pre-parsing produced accepted result */
+ const char *key = pb->data;
+ const int keylen = pb->srccall_end - key;
+ uint32_t hash;
+ int idx;
+ char uckey[CALLSIGNLEN_MAX+1];
+
+ /* If it is not a WX packet without position, we are not intrerested */
+ if (!((pb->packettype & T_WX) && !(pb->flags & F_HASPOS)))
+ return 0;
+
+ for (idx = 0; idx <= keylen && idx < CALLSIGNLEN_MAX; ++idx) {
+ int c = key[idx];
+ if (c == ',' || c == ':')
+ break;
+ if ('a' <= c && c <= 'z')
+ c -= ('a' - 'A');
+ uckey[idx] = c;
+ uckey[idx+1] = 0;
+ }
+
+ hash = keyhash(uckey, keylen, 0);
+ idx = ( hash ^ (hash >> 10) ^ (hash >> 20) ) % FILTER_WX_HASHSIZE; /* fold the hashbits.. */
+
+ fp = &filter_wx_hash[idx];
+ f2 = NULL;
+ while (( f = *fp )) {
+ if ( f->hash == hash ) {
+ if (f->len == keylen) {
+ int cmp = memcmp(f->callsign, uckey, keylen);
+ if (cmp == 0) { /* Have key match */
+ f->expirytime = tick.tv_sec + filter_wx_maxage;
+ f2 = f;
+ break;
+ }
+ }
+ }
+ /* No match at all, advance the pointer.. */
+ fp = &(f -> next);
+ }
+ if (!f2) {
+
+ /* Allocate and insert into hash table */
+
+ fp = &filter_wx_hash[idx];
+
+#ifndef _FOR_VALGRIND_
+ f = cellmalloc(filter_wx_cells);
+#else
+ f = calloc(1, sizeof(*f));
+#endif
+ ++filter_wx_cellgauge;
+ if (f) {
+ f->next = *fp;
+ f->expirytime = tick.tv_sec + filter_wx_maxage;
+ f->hash = hash;
+ f->len = keylen;
+ memcpy(f->callsign, key, keylen);
+ memset(f->callsign+keylen, 0, sizeof(f->callsign)-keylen);
+
+ *fp = f2 = f;
+ }
+ }
+
+ return 0;
+}
+
+static int filter_wx_lookup(const struct pbuf_t *pb)
+{
+ struct filter_wx_t *f, **fp, *f2;
+ const char *key = pb->data;
+ const int keylen = pb->srccall_end - key;
+
+ uint32_t hash = keyhashuc(key, keylen, 0);
+ int idx = ( hash ^ (hash >> 10) ^ (hash >> 20) ) % FILTER_WX_HASHSIZE; /* fold the hashbits.. */
+
+ f2 = NULL;
+
+ fp = &filter_wx_hash[idx];
+ while (( f = *fp )) {
+ if ( f->hash == hash ) {
+ if (f->len == keylen) {
+ int rc = strncasecmp(f->callsign, key, keylen);
+ if (rc == 0) { /* Have key match, see if it is
+ still valid entry ? */
+ if ((f->expirytime - (tick.tv_sec - 60)) < 0) {
+ f2 = f;
+ break;
+ }
+ }
+ }
+ }
+ /* No match at all, advance the pointer.. */
+ fp = &(f -> next);
+ }
+
+ return (f2 != NULL);
+}
+
+
+/*
+ * The filter_wx_cleanup() does purge old entries
+ * out of the database. Run about once a minute.
+ */
+void filter_wx_cleanup(void)
+{
+ int k, cleancount = 0;
+ struct filter_wx_t *f, **fp;
+
+ for (k = 0; k < FILTER_WX_HASHSIZE; ++k) {
+ fp = & filter_wx_hash[k];
+ while (( f = *fp )) {
+ /* Did it expire ? */
+ if ((f->expirytime - tick.tv_sec) <= 0) {
+ *fp = f->next;
+ f->next = NULL;
+ filter_wx_free(f);
+ ++cleancount;
+ continue;
+ }
+ /* No purge, advance the pointer.. */
+ fp = &(f -> next);
+ }
+ }
+
+ // hlog( LOG_DEBUG, "filter_wx_cleanup() removed %d entries, count now: %ld",
+ // cleancount, filter_wx_cellgauge );
+}
+
+/*
+ * The filter_wx_atend() does purge all entries
+ * out of the database. Run at the exit of the program.
+ * This exists primarily to make valgrind happy...
+ */
+void filter_wx_atend(void)
+{
+ int k;
+ struct filter_wx_t *f, **fp;
+
+ for (k = 0; k < FILTER_WX_HASHSIZE; ++k) {
+ fp = & filter_wx_hash[k];
+ while (( f = *fp )) {
+ *fp = f->next;
+ f->next = NULL;
+ filter_wx_free(f);
+ }
+ }
+}
+
+
+void filter_wx_dump(FILE *fp)
+{
+ int k;
+ struct filter_wx_t *f;
+
+ for (k = 0; k < FILTER_WX_HASHSIZE; ++k) {
+ f = filter_wx_hash[k];
+
+ for ( ; f; f = f->next ) {
+ fprintf( fp, "%ld\t%s\n",
+ (long)f->expirytime, f->callsign );
+ }
+ }
+}
+#endif
+
+/* ================================================================ */
+
+
+void filter_preprocess_dupefilter(struct pbuf_t *pbuf)
+{
+#if 0
+ filter_entrycall_insert(pbuf);
+ filter_wx_insert(pbuf);
+#endif
+}
+
+void filter_postprocess_dupefilter(struct pbuf_t *pbuf, historydb_t *historydb)
+{
+ /*
+ * If there is no position at this packet from earlier
+ * processing, try now to find one by the callsign of
+ * the packet sender.
+ *
+ */
+#ifndef DISABLE_IGATE
+ if (!(pbuf->flags & F_HASPOS)) {
+ history_cell_t *hist;
+ hist = historydb_lookup(historydb, pbuf->srcname, pbuf->srcname_len);
+ // hlog( LOG_DEBUG, "postprocess_dupefilter: no pos, looking up '%.*s', rc=%d",
+ // pbuf->srcname_len, pbuf->srcname, rc );
+ if (hist != NULL) {
+ pbuf->lat = hist->lat;
+ pbuf->lng = hist->lon;
+ pbuf->cos_lat = hist->coslat;
+
+ pbuf->flags |= F_HASPOS;
+ }
+ }
+#endif
+}
+
+
+/* ================================================================ */
+
+/*
+ * filter_match_on_callsignset() matches prefixes, or exact keys
+ * on filters of types: b, d, e, o, p, u
+ * ('p' and 'b' need OPTIMIZATION - others get it for free)
+ *
+ */
+
+static int filter_match_on_callsignset(struct filter_refcallsign_t *ref, int keylen, struct filter_t *f, const MatchEnum wildok)
+{
+ int i;
+ struct filter_refcallsign_t *r = f->h.u5.refcallsigns;
+ const char *r1 = (const void*)ref->callsign;
+
+ if (debug) printf(" filter_match_on_callsignset(ref='%s', keylen=%d, filter='%s')\n", ref->callsign, keylen, f->h.text);
+
+ for (i = 0; i < f->h.u3.numnames; ++i) {
+ const int reflen = r[i].reflen;
+ const int len = reflen & LengthMask;
+ const char *r2 = (const void*)r[i].callsign;
+
+ if (debug)printf(" .. reflen=0x%02x r2='%s'\n", reflen & 0xFF, r2);
+
+
+ switch (wildok) {
+ case MatchExact:
+ if (len != keylen)
+ continue; /* no match */
+ /* length OK, compare content */
+ if (strncasecmp( r1, r2, len ) != 0) continue;
+ /* So it was an exact match
+ ** Precisely speaking.. we should check that there is
+ ** no WildCard flag, or such. But then this match
+ ** method should not be used if parser finds any such.
+ */
+ return ( reflen & NegationFlag ? 2 : 1 );
+ break;
+ case MatchPrefix:
+ if (len > keylen || !len) {
+ /* reference string length is longer than our key */
+ continue;
+ }
+ if (strncasecmp( r1, r2, len ) != 0) continue;
+
+ return ( reflen & NegationFlag ? 2 : 1 );
+ break;
+ case MatchWild:
+ if (len > keylen || !len) {
+ /* reference string length is longer than our key */
+ continue;
+ }
+
+ if (strncasecmp( r1, r2, len ) != 0) continue;
+
+ if (reflen & WildCard)
+ return ( reflen & NegationFlag ? 2 : 1 );
+
+ if (len == keylen)
+ return ( reflen & NegationFlag ? 2 : 1 );
+ break;
+ default:
+ break;
+ }
+ }
+ return 0; /* no match */
+}
+
+/*
+ * filter_parse_one_callsignset() collects multiple callsigns
+ * on filters of types: b, d, e, o, p, u
+ *
+ * If previous filter was of same type as this one, that one's refbuf is extended.
+ */
+
+static int filter_parse_one_callsignset(struct filter_t **ffp, struct filter_t *f0, const char *filt0, MatchEnum wildok)
+{
+ char prefixbuf[CALLSIGNLEN_MAX+1];
+ char *k;
+ const char *p;
+ int i, refcount, wildcard;
+ int refmax = 0, extend = 0;
+ struct filter_refcallsign_t *refbuf;
+ struct filter_t *ff = *ffp;
+
+ p = filt0;
+ if (*p == '-') ++p;
+ // Skip the first '/'
+ while (*p && *p != '/') ++p;
+ if (*p == '/') ++p;
+ /* count the number of prefixes in there.. */
+ while (*p) {
+ if (*p) ++refmax;
+ while (*p && *p != '/') ++p;
+ if (*p == '/') ++p;
+ }
+ if (refmax == 0) {
+ printf("Filter definition of '%s' has no prefixes defined.\n", filt0);
+ return -1; /* No prefixes ?? */
+ }
+
+ if (ff && ff->h.type == f0->h.type) { /* SAME TYPE,
+ extend previous record! */
+ extend = 1;
+ refcount = ff->h.u3.numnames + refmax;
+ refbuf = realloc(ff->h.u5.refcallsigns, sizeof(*refbuf) * refcount);
+ ff->h.u5.refcallsigns = refbuf;
+ refcount = ff->h.u3.numnames;
+ } else {
+ refbuf = calloc(1, sizeof(*refbuf)*refmax);
+ refcount = 0;
+ f0->h.u5.refcallsigns = refbuf;
+ f0->h.u3.numnames = 0;
+ }
+
+ p = filt0;
+ if (*p == '-') ++p;
+ // Skip the first '/'
+ while (*p && *p != '/') ++p;
+ if (*p == '/') ++p;
+
+ /* hlog(LOG_DEBUG, "p-filter: '%s' vs. '%s'", p, keybuf); */
+ while (*p) {
+ k = prefixbuf;
+ memset(prefixbuf, 0, sizeof(prefixbuf));
+ i = 0;
+ wildcard = 0;
+ while (*p != 0 && *p != '/') {
+ int c = *p++;
+ if (c == '*') {
+ wildcard = 1;
+ if (wildok != MatchWild) {
+ printf("Wild-card matching not permitted, yet filter definition says: '%s'\n", filt0);
+ return -1;
+ }
+ continue;
+ }
+ if (i < CALLSIGNLEN_MAX) {
+ *k++ = c;
+ ++i;
+ } else {
+ printf("Too long callsign string: '%s' input: '%s'\n", prefixbuf, filt0);
+ return -1; // invalid input
+ }
+ }
+ *k = 0;
+ /* OK, we have one prefix part collected, scan source until next '/' */
+ if (*p != 0 && *p != '/') ++p;
+ if (*p == '/') ++p;
+ /* If there is more of patterns, the loop continues.. */
+
+ /* Store the refprefix */
+ memset(&refbuf[refcount], 0, sizeof(refbuf[refcount]));
+ memcpy(refbuf[refcount].callsign, prefixbuf, sizeof(refbuf[refcount].callsign));
+ refbuf[refcount].reflen = strlen(prefixbuf);
+ if (wildcard)
+ refbuf[refcount].reflen |= WildCard;
+ if (f0->h.negation)
+ refbuf[refcount].reflen |= NegationFlag;
+ ++refcount;
+ }
+
+ f0->h.u3.numnames = refcount;
+ if (extend) {
+ char *s;
+ ff->h.u3.numnames = refcount;
+ i = strlen(ff->h.text) + strlen(filt0)+2;
+ if (i <= FILT_TEXTBUFSIZE) {
+ /* Fits in our built-in buffer block - like previous..
+ ** Append on existing buffer
+ */
+ s = ff->textbuf + strlen(ff->textbuf);
+ sprintf(s, " %s", filt0);
+ } else {
+ /* It does not fit anymore.. */
+ s = malloc(i); /* alloc a new one */
+ sprintf(s, "%s %s", p, filt0); /* .. and catenate. */
+ p = ff->h.text;
+ if (ff->h.text != ff->textbuf) /* possibly free old */
+ free((void*)p);
+ ff->h.text = s; /* store new */
+ }
+ }
+ /* If not extending existing filter item, let main parser do the finalizations */
+
+ return extend;
+}
+
+int filter_parse_one_s(struct filter_t *f0, struct filter_t **ffp, const char *filt0)
+{
+ /* s/pri/alt/over Symbol filter
+
+ pri = symbols in primary table
+ alt = symbols in alternate table
+ over = overlay character (case sensitive)
+
+ For example:
+ s/-> This will pass all House and Car symbols (primary table)
+ s//# This will pass all Digi with or without overlay
+ s//#/T This will pass all Digi with overlay of capital T
+
+ About 10-15 s-filters in entire APRS-IS core at any given time.
+ Up to 520 invocations per second at peak.
+ */
+ const char *s = filt0;
+ // struct filter_t *ff = *ffp;
+ int len1, len2, len3, len4, len5, len6;
+
+ if (*s == '-')
+ ++s;
+ if (*s == 's' || *s == 'S')
+ ++s;
+ if (*s != '/')
+ return -1;
+ ++s;
+
+ len1 = len2 = len3 = len4 = len5 = len6 = 0;
+
+ while (1) {
+
+ len1 = s - filt0;
+ while (*s && *s != '/') ++s;
+ len2 = s - filt0;
+
+ f0->h.u3.len1s = len1;
+ f0->h.u4.len1 = len2 - len1;
+ f0->h.u5.lens.len2s = f0->h.u5.lens.len2 = f0->h.u5.lens.len3s = f0->h.u5.lens.len3 = 0;
+
+ if (!*s) break;
+
+ if (*s == '/') ++s;
+ len3 = s - filt0;
+ while (*s && *s != '/') ++s;
+ len4 = s - filt0;
+
+ f0->h.u5.lens.len2s = len3;
+ f0->h.u5.lens.len2 = len4 - len3;
+
+ if (!*s) break;
+
+ if (*s == '/') ++s;
+ len5 = s - filt0;
+ while (*s) ++s;
+ len6 = s - filt0;
+
+ f0->h.u5.lens.len3s = len5;
+ f0->h.u5.lens.len3 = len6 - len5;
+
+ break;
+ }
+
+ if ((len6-len5 > 0) && (len4-len3 == 0)) {
+ /* overlay but no secondary table.. */
+ return -1; /* bad parse */
+ }
+#if 0
+ {
+ const char *s1 = filt0+len1, *s2 = filt0+len3, *s3 = filt0+len5;
+ int l1 = len2-len1, l2 = len4-len3, l3 = len6-len5;
+
+ // hlog( LOG_DEBUG, "parse s-filter: '%.*s' '%.*s' '%.*s'", l1, s1, l2, s2, l3, s3 );
+ }
+#endif
+ return 0;
+}
+
+
+int filter_parse(struct filter_t **ffp, const char *filt)
+{
+ struct filter_t f0;
+ int i;
+ const char *filt0 = filt;
+ const char *s;
+ char dummyc, dummy2;
+ struct filter_t *ff, *f;
+
+ ff = *ffp;
+ for ( ; ff && ff->h.next; ff = ff->h.next)
+ ;
+ /* ff points to last so far accumulated filter,
+ if none were previously received, it is NULL.. */
+
+ memset(&f0, 0, sizeof(f0));
+ if (*filt == '-') {
+ f0.h.negation = 1;
+ ++filt;
+ }
+ f0.h.type = *filt;
+
+ if (!strchr("abdefmopqrstuABDEFMOPQRSTU",*filt)) {
+ // Not valid filter code
+ // hlog(LOG_DEBUG, "Bad filter code: %s", filt0);
+ if (debug)
+ printf("Bad filter code: %s\n", filt0);
+ return -1;
+ }
+
+ switch (f0.h.type) {
+ case 'a':
+ case 'A':
+ /* a/latN/lonW/latS/lonE Area filter -- OPTIMIZE! */
+
+ f0.h.type = 'a'; // inside area
+
+ i = sscanf(filt+1, "/%f/%f/%f/%f%c%c",
+ &f0.h.f_latN, &f0.h.u2.f_lonW,
+ &f0.h.u1.f_latS, &f0.h.f_lonE, &dummyc, &dummy2);
+
+ if (i == 6 && dummyc == '/' && dummy2 == '-') {
+ i = 4;
+ f0.h.type = 'A'; // outside area!
+ }
+ if (i == 5 && dummyc == '-') {
+ i = 4;
+ f0.h.type = 'A'; // outside area!
+ }
+ if (i == 5 && dummyc == '/') {
+ i = 4;
+ }
+
+ if (i != 4) {
+ // hlog(LOG_DEBUG, "Bad filter parse: %s", filt0);
+ if (debug)
+ printf("Bad filter parse: %s", filt0);
+ return -1;
+ }
+
+ if (!( -90.01 < f0.h.f_latN && f0.h.f_latN < 90.01)) {
+ // hlog(LOG_DEBUG, "Bad filter latN value: %s", filt0);
+ if (debug)
+ printf("Bad filter latN value: %s", filt0);
+ return -2;
+ }
+ if (!(-180.01 < f0.h.u2.f_lonW && f0.h.u2.f_lonW < 180.01)) {
+ // hlog(LOG_DEBUG, "Bad filter lonW value: %s", filt0);
+ if (debug)
+ printf("Bad filter lonW value: %s", filt0);
+ return -2;
+ }
+ if (!( -90.01 < f0.h.u1.f_latS && f0.h.u1.f_latS < 90.01)) {
+ // hlog(LOG_DEBUG, "Bad filter latS value: %s", filt0);
+ if (debug)
+ printf("Bad filter latS value: %s", filt0);
+ return -2;
+ }
+ if (!(-180.01 < f0.h.f_lonE && f0.h.f_lonE < 180.01)) {
+ // hlog(LOG_DEBUG, "Bad filter lonE value: %s", filt0);
+ if (debug)
+ printf("Bad filter lonE value: %s", filt0);
+ return -2;
+ }
+
+ if (f0.h.u2.f_lonW > f0.h.f_lonE) {
+ // wrong way, swap longitudes
+ float t = f0.h.u2.f_lonW;
+ f0.h.u2.f_lonW = f0.h.f_lonE;
+ f0.h.f_lonE = t;
+ }
+ if (f0.h.u1.f_latS > f0.h.f_latN) {
+ // wrong way, swap latitudes
+ float t = f0.h.u1.f_latS;
+ f0.h.u1.f_latS = f0.h.f_latN;
+ f0.h.f_latN = t;
+ }
+
+ // hlog(LOG_DEBUG, "Filter: %s -> A %.3f %.3f %.3f %.3f", filt0, f0.h.f_latN, f0.h.f_lonW, f0.h.f_latS, f0.h.f_lonE);
+
+ f0.h.f_latN = filter_lat2rad(f0.h.f_latN);
+ f0.h.u2.f_lonW = filter_lon2rad(f0.h.u2.f_lonW);
+
+ f0.h.u1.f_latS = filter_lat2rad(f0.h.u1.f_latS);
+ f0.h.f_lonE = filter_lon2rad(f0.h.f_lonE);
+
+ break;
+
+ case 'b':
+ case 'B':
+ /* b/call1/call2... Budlist filter (*) */
+
+ i = filter_parse_one_callsignset(ffp, &f0, filt0, MatchWild );
+ if (i < 0)
+ return i;
+ if (i > 0) /* extended previous */
+ return 0;
+
+
+ break;
+ case 'd':
+ case 'D':
+ /* d/digi1/digi2... Digipeater filter (*) */
+
+ i = filter_parse_one_callsignset(ffp, &f0, filt0, MatchWild );
+ if (i < 0)
+ return i;
+ if (i > 0) /* extended previous */
+ return 0;
+
+ break;
+#if 0
+ case 'e':
+ case 'E':
+ /* e/call1/call1/... Entry station filter (*) */
+
+ i = filter_parse_one_callsignset(ffp, &f0, filt0, MatchWild );
+ if (i < 0)
+ return i;
+ if (i > 0) /* extended previous */
+ return 0;
+
+ break;
+#endif
+ case 'f':
+ case 'F':
+ /* f/call/dist Friend's range filter */
+
+ i = sscanf(filt+1, "/%9[^/]/%f", f0.h.u5.refcallsign.callsign, &f0.h.u2.f_dist);
+ // negative distance means "outside this range."
+ // and makes most sense with overall negative filter!
+ if (i != 2 || (-0.1 < f0.h.u2.f_dist && f0.h.u2.f_dist < 0.1)) {
+ // hlog(LOG_DEBUG, "Bad filter parse: %s", filt0);
+ if (debug)
+ printf("Bad filter parse: %s", filt0);
+ return -1;
+ }
+
+ f0.h.u5.refcallsign.callsign[CALLSIGNLEN_MAX] = 0;
+ f0.h.u5.refcallsign.reflen = strlen(f0.h.u5.refcallsign.callsign);
+ f0.h.u3.numnames = 0; /* reusing this as "position-cache valid" flag */
+
+ // hlog(LOG_DEBUG, "Filter: %s -> F xxx %.3f", filt0, f0.h.u2.f_dist);
+
+ /* NOTE: Could do static location resolving at connect time,
+ ** and then use the same way as 'r' range does. The friends
+ ** are rarely moving...
+ */
+
+ break;
+
+ case 'g':
+ case 'G':
+ // g/call1/call2/ Group Messaging filter
+ i = filter_parse_one_callsignset(ffp, &f0, filt0, MatchWild );
+ if (i < 0)
+ return i;
+ if (i > 0) /* extended previous */
+ return 0;
+ break;
+
+ case 'm':
+ case 'M':
+ /* m/dist My range filter */
+
+ if (myloc_latstr == NULL) {
+ printf("The M/radius_km filter requires top-level myloc definition. It doesn't exist.\n");
+ return -1;
+ }
+
+ f0.h.type = 'r'; // internal implementation at Aprx is a RANGE filter.
+ f0.h.f_latN = myloc_lat; // radians
+ f0.h.f_lonE = myloc_lon; // radians
+ f0.h.u1.f_coslat = myloc_coslat;
+
+ i = sscanf(filt+1, "/%f", &f0.h.u2.f_dist);
+ if (i != 1 || f0.h.u2.f_dist < 0.1) {
+ // hlog(LOG_DEBUG, "Bad filter parse: %s", filt0);
+ if (debug)
+ printf("Bad filter parse: %s", filt0);
+ return -1;
+ }
+ f0.h.u3.numnames = 0; /* reusing this as "position-cache valid" flag */
+
+ // hlog(LOG_DEBUG, "Filter: %s -> M %.3f", filt0, f0.h.u2.f_dist);
+ break;
+
+ case 'o':
+ case 'O':
+ /* o/obje1/obj2... Object filter (*) */
+
+ i = filter_parse_one_callsignset(ffp, &f0, filt0, MatchWild );
+ if (i < 0)
+ return i;
+ if (i > 0) /* extended previous */
+ return 0;
+
+ break;
+
+ case 'p':
+ case 'P':
+ /* p/aa/bb/cc... Prefix filter
+ Pass traffic with fromCall that start with aa or bb or cc...
+ */
+ i = filter_parse_one_callsignset(ffp, &f0, filt0, MatchWild );
+ if (i < 0)
+ return i;
+ if (i > 0) /* extended previous */
+ return 0;
+
+ break;
+#if 0
+ case 'q':
+ case 'Q':
+ /* q/con/ana q Contruct filter */
+ s = filt+1;
+ f0.h.type = 'q';
+ f0.h.u4.bitflags = 0; /* For QC_* flags */
+
+ if (*s++ != '/') {
+ // hlog(LOG_DEBUG, "Bad q-filter parse: %s", filt0);
+ if (debug)
+ printf("Bad q-filter parse: %s", filt0);
+ return -1;
+ }
+ for ( ; *s && *s != '/'; ++s ) {
+ switch (*s) {
+ case 'C':
+ f0.h.u4.bitflags |= QC_C;
+ break;
+ case 'X':
+ f0.h.u4.bitflags |= QC_X;
+ break;
+ case 'U':
+ f0.h.u4.bitflags |= QC_U;
+ break;
+ case 'o':
+ f0.h.u4.bitflags |= QC_o;
+ break;
+ case 'O':
+ f0.h.u4.bitflags |= QC_O;
+ break;
+ case 'S':
+ f0.h.u4.bitflags |= QC_S;
+ break;
+ case 'r':
+ f0.h.u4.bitflags |= QC_r;
+ break;
+ case 'R':
+ f0.h.u4.bitflags |= QC_R;
+ break;
+ case 'Z':
+ f0.h.u4.bitflags |= QC_Z;
+ break;
+ case 'I':
+ f0.h.u4.bitflags |= QC_I;
+ break;
+ default:
+ // hlog(LOG_DEBUG, "Bad q-filter parse: %s", filt0);
+ if (debug)
+ printf("Bad q-filter parse: %s", filt0);
+ return -1;
+ }
+ }
+ if (*s == '/') { /* second format */
+ ++s;
+ if (*s == 'i' || *s == 'I') {
+ f0.h.u4.bitflags |= QC_AnalyticsI;
+ ++s;
+ }
+ if (*s) {
+ // hlog(LOG_DEBUG, "Bad q-filter parse: %s", filt0);
+ if (debug)
+ printf("Bad q-filter parse: %s", filt0);
+ return -1;
+ }
+ }
+
+ break;
+#endif
+ case 'r':
+ case 'R':
+ /* r/lat/lon/dist Range filter */
+
+ i = sscanf(filt+1, "/%f/%f/%f",
+ &f0.h.f_latN, &f0.h.f_lonE, &f0.h.u2.f_dist);
+ // negative distance means "outside this range."
+ // and makes most sense with overall negative filter!
+ if (i != 3 || (-0.1 < f0.h.u2.f_dist && f0.h.u2.f_dist < 0.1)) {
+ // hlog(LOG_DEBUG, "Bad filter parse: %s", filt0);
+ if (debug)
+ printf("Bad filter parse: %s", filt0);
+ return -1;
+ }
+
+ if (!( -90.01 < f0.h.f_latN && f0.h.f_latN < 90.01)) {
+ // hlog(LOG_DEBUG, "Bad filter lat value: %s", filt0);
+ if (debug)
+ printf("Bad filter lat value: %s", filt0);
+ return -2;
+ }
+ if (!(-180.01 < f0.h.f_lonE && f0.h.f_lonE < 180.01)) {
+ // hlog(LOG_DEBUG, "Bad filter lon value: %s", filt0);
+ if (debug)
+ printf("Bad filter lon value: %s", filt0);
+ return -2;
+ }
+
+ // hlog(LOG_DEBUG, "Filter: %s -> R %.3f %.3f %.3f", filt0, f0.h.f_latN, f0.h.f_lonE, f0.h.u2.f_dist);
+
+ f0.h.f_latN = filter_lat2rad(f0.h.f_latN);
+ f0.h.f_lonE = filter_lon2rad(f0.h.f_lonE);
+
+ f0.h.u1.f_coslat = cosf( f0.h.f_latN ); /* Store pre-calculated COS of LAT */
+ break;
+
+ case 's':
+ case 'S':
+ /* s/pri/alt/over Symbol filter */
+
+ i = filter_parse_one_s( &f0, ffp, filt0 );
+ if (i < 0) {
+ // hlog(LOG_DEBUG, "Bad s-filter syntax: %s", filt0);
+ if (debug)
+ printf("Bad s-filter syntax: %s", filt0);
+ return i;
+ }
+ if (i > 0) /* extended previous */
+ return 0;
+ break;
+
+ case 't':
+ case 'T':
+ /* t/..............
+ t/............../call/km
+ */
+ s = filt+1;
+ f0.h.type = 't';
+ f0.h.u4.bitflags = 0;
+ f0.h.u3.numnames = 0; /* reusing this as "position-cache valid" flag */
+
+ if (*s++ != '/') {
+ // hlog(LOG_DEBUG, "Bad filter parse: %s", filt0);
+ if (debug)
+ printf("Bad t-filter syntax: %s", filt0);
+ return -1;
+ }
+ for ( ; *s && *s != '/'; ++s ) {
+ switch (*s) {
+ case '*':
+ f0.h.u4.bitflags |= ~T_CWOP; /* "ALL" -- excluding CWOP */
+ break;
+ case '3':
+ f0.h.u4.bitflags |= T_THIRDPARTY;
+ break;
+ case 'c': case 'C':
+ f0.h.u4.bitflags |= T_CWOP;
+ break;
+ case 'i': case 'I':
+ f0.h.u4.bitflags |= T_ITEM;
+ break;
+ case 'm': case 'M':
+ f0.h.u4.bitflags |= T_MESSAGE;
+ break;
+ case 'n': case 'N':
+ f0.h.u4.bitflags |= T_NWS;
+ break;
+ case 'o': case 'O':
+ f0.h.u4.bitflags |= T_OBJECT;
+ break;
+ case 'p': case 'P':
+ f0.h.u4.bitflags |= T_POSITION;
+ break;
+ case 'q': case 'Q':
+ f0.h.u4.bitflags |= T_QUERY;
+ break;
+ case 's': case 'S':
+ f0.h.u4.bitflags |= T_STATUS;
+ break;
+ case 't': case 'T':
+ f0.h.u4.bitflags |= T_TELEMETRY;
+ break;
+ case 'u': case 'U':
+ f0.h.u4.bitflags |= T_USERDEF;
+ break;
+ case 'w': case 'W':
+ f0.h.u4.bitflags |= T_WX;
+ break;
+ default:
+ // hlog(LOG_DEBUG, "Bad filter parse: %s", filt0);
+ if (debug)
+ printf("Bad t-filter syntax: %s", filt0);
+ return -1;
+ }
+ }
+ if (*s == '/' && s[1] != 0) { /* second format */
+ i = sscanf(s, "/%9[^/]/%f%c", f0.h.u5.refcallsign.callsign, &f0.h.u2.f_dist, &dummyc);
+ // negative distance means "outside this range."
+ // and makes most sense with overall negative filter!
+ if ( i != 2 || (-0.1 < f0.h.u2.f_dist && f0.h.u2.f_dist < 0.1) || /* 0.1 km minimum radius */
+ strlen(f0.h.u5.refcallsign.callsign) < CALLSIGNLEN_MIN ) {
+ // hlog(LOG_DEBUG, "Bad filter parse: %s", filt0);
+ if (debug)
+ printf("Bad t-filter parse: %s", filt0);
+ return -1;
+ }
+ f0.h.u5.refcallsign.callsign[CALLSIGNLEN_MAX] = 0;
+ f0.h.u5.refcallsign.reflen = strlen(f0.h.u5.refcallsign.callsign);
+ f0.h.type = 'T'; /* two variants... */
+ }
+
+ break;
+
+ case 'u':
+ case 'U':
+ /* u/unproto1/unproto2... Unproto filter (*) */
+
+ i = filter_parse_one_callsignset(ffp, &f0, filt0, MatchWild );
+ if (i < 0)
+ return i;
+ if (i > 0) /* extended previous */
+ return 0;
+
+ break;
+
+
+
+ default:;
+ /* No pre-parsers for other types */
+ // hlog(LOG_DEBUG, "Filter: %s", filt0);
+ if (debug)
+ printf("Bad filter code: %s\n", filt0);
+ return -1;
+ break;
+ }
+
+ // if (!c) return 0; /* Just a verification scan, not actual fill in parse */
+
+ /* OK, pre-parsing produced accepted result */
+#ifndef _FOR_VALGRIND_
+ f = cellmalloc(filter_cells);
+ if (!f) return -1;
+ *f = f0; /* store pre-parsed values */
+ if (strlen(filt0) < FILT_TEXTBUFSIZE) {
+ strcpy(f->textbuf, filt0);
+ f->h.text = f->textbuf;
+ } else
+ f->h.text = strdup(filt0); /* and copy of filter text */
+#else
+ f = calloc(1, sizeof(*f) + strlen(filt0));
+ *f = f0; /* store pre-parsed values */
+ f->h.text = f->textbuf;
+ strcpy(f->textbuf, filt); /* and copy of filter text */
+#endif
+
+ /* hlog(LOG_DEBUG, "parsed filter: t=%c n=%d '%s'", f->h.type, f->h.negation, f->h.text); */
+
+ /* link to the tail.. */
+ if (ff)
+ ffp = &ff->h.next;
+
+ *ffp = f;
+
+ return 0;
+}
+
+/* Discard the defined filter chain */
+void filter_free(struct filter_t *f)
+{
+ struct filter_t *fnext;
+
+ for ( ; f ; f = fnext ) {
+ fnext = f->h.next;
+ /* If not pointer to internal string, free it.. */
+#ifndef _FOR_VALGRIND_
+ if (f->h.text != f->textbuf)
+ free((void*)(f->h.text));
+ cellfree(filter_cells, f);
+#else
+ free(f);
+#endif
+ }
+}
+
+
+/*
+
+#
+# Input: This[La] Source Latitude, in radians
+# This[Lo] Source Longitude, in radians
+# That[La] Destination Latitude, in radians
+# That[Lo] Destination Longitude, in radians
+# Output: R[s] Distance, in kilometers
+#
+
+function maidenhead_km_distance($This, $That) {
+
+ #Haversine Formula (from R.W. Sinnott, "Virtues of the Haversine",
+ #Sky and Telescope, vol. 68, no. 2, 1984, p. 159):
+
+ $dlon = $That[Lo] - $This[Lo];
+ $dlat = $That[La] - $This[La];
+
+ $sinDlat2 = sin($dlat/2);
+ $sinDlon2 = sin($dlon/2);
+ $a = ($sinDlat2 * $sinDlat2 +
+ cos($This[La]) * cos($That[La]) * $sinDlon2 * $sinDlon2);
+
+ # The Haversine Formula can be expressed in terms of a two-argument
+ # inverse tangent function, atan2(y,x), instead of an inverse sine
+ # as follows (no bulletproofing is needed for an inverse tangent):
+
+ $c = 2.0 * atan2( sqrt($a), sqrt(1.0-$a) );
+ # $d = R * $c ; # Radius of ball times angle [radians] ...
+
+
+ $R[s] = rad2deg($c) * 111.2;
+
+ return($R);
+
+}
+
+*/
+
+static float maidenhead_km_distance(float lat1, float coslat1, float lon1, float lat2, float coslat2, float lon2)
+{
+ float sindlat2 = sinf((lat1 - lat2) * 0.5);
+ float sindlon2 = sinf((lon1 - lon2) * 0.5);
+
+ float a = (sindlat2 * sindlat2 +
+ coslat1 * coslat2 * sindlon2 * sindlon2);
+
+ float c = 2.0 * atan2f( sqrtf(a), sqrtf(1.0 - a));
+
+ return ((111.2 * 180.0 / M_PI) * c);
+}
+
+
+/*
+ *
+ * http://www.aprs-is.net/javaprssrvr/javaprsfilter.htm
+ *
+ */
+
+static int filter_process_one_a(struct pbuf_t *pb, struct filter_t *f)
+{
+ /* a/latN/lonW/latS/lonE Area filter
+
+ The area filter works the same as range filter but the filter
+ is defined as a box of coordinates. The coordinates can also
+ been seen as upper left coordinate and lower right. Lat/lon
+ are decimal degrees. South and west are negative.
+
+ Multiple area filters can be defined at the same time.
+
+ Messages addressed to stations within the area are also passed.
+ (by means of aprs packet parse finding out the location..)
+
+ 50-70 instances in APRS-IS core at any given time.
+ Up to 2500 invocations per second.
+ */
+ ;
+ if (!(pb->flags & F_HASPOS)) /* packet with a position.. (msgs with RECEIVER's position) */
+ return 0;
+
+ if ((pb->lat <= f->h.f_latN) &&
+ (pb->lat >= f->h.u1.f_latS) &&
+ (pb->lng <= f->h.f_lonE) && /* East POSITIVE ! */
+ (pb->lng >= f->h.u2.f_lonW)) {
+ /* Inside the box */
+ return f->h.negation ? 2 : 1;
+ } else if (f->h.type == 'A') {
+ /* Outside the box */
+ return f->h.negation ? 2 : 1;
+ }
+
+ return 0;
+}
+
+static int filter_process_one_b(struct pbuf_t *pb, struct filter_t *f)
+{
+ /* b/call1/call2... Budlist filter
+
+ Pass all traffic FROM exact call: call1, call2, ...
+ (* wild card allowed)
+
+ 50/70 instances in APRS-IS core at any given time.
+ Up to 2500 invocations per second.
+ */
+
+ struct filter_refcallsign_t ref;
+ int i = pb->srccall_end - pb->data;
+
+ if (i > CALLSIGNLEN_MAX) i = CALLSIGNLEN_MAX;
+
+ /* source address "addr">... */
+ memset( &ref, 0, sizeof(ref) ); // clear it all
+ memcpy( ref.callsign, pb->data, i);
+
+ return filter_match_on_callsignset(&ref, i, f, MatchWild);
+}
+
+static int filter_process_one_d(struct pbuf_t *pb, struct filter_t *f)
+{
+ /* d/digi1/digi2... Digipeater filter
+
+ The digipeater filter will match all packets that have been
+ digipeated by a particular station(s) (the station's call
+ is in the path). This filter allows the * wildcard.
+
+ 25-35 instances in APRS-IS core at any given time.
+ Up to 1300 invocations per second.
+ */
+ struct filter_refcallsign_t ref;
+ const char *d = pb->srccall_end + 1 + pb->dstcall_len + 1; /* viacall start */
+ const char *q = pb->qconst_start-1;
+ int rc, i, j = 0;
+
+ // hlog( LOG_INFO, "digifilter: '%.*s' -> '%.*s' q-d=%d",
+ // (int)(pb->packet_len < 50 ? pb->packet_len : 50),
+ // pb->data, (int)i, d, (int)(q-d) );
+
+ for (i = 0; d < q; ) {
+ ++j;
+ if (j > 10) break; // way too many callsigns... (code bug?)
+
+ if (*d == ',') ++d; // second round and onwards..
+ for (i = 0; i+d <= q && i <= CALLSIGNLEN_MAX; ++i) {
+ if (d[i] == ',')
+ break;
+ }
+
+ // hlog(LOG_INFO, "d: -> (%d,%d) '%.*s'", (int)(d-pb->data), i, i, d);
+
+ // digipeater address ",addr,"
+ memset( &ref, 0, sizeof(ref) ); // clear it all
+ memcpy( ref.callsign, d, i);
+
+ if (i > CALLSIGNLEN_MAX) i = CALLSIGNLEN_MAX;
+
+ rc = filter_match_on_callsignset(&ref, i, f, MatchWild);
+ if (rc) {
+ return (rc == 1);
+ }
+ d += i;
+ }
+ return 0;
+}
+
+#if 0
+static int filter_process_one_e(struct pbuf_t *pb, struct filter_t *f)
+{
+ /* e/call1/call1/... Entry station filter
+
+ This filter matches all packets with the specified
+ callsign-SSID(s) immediately following the q construct.
+ This allows filtering based on receiving IGate, etc.
+ Supports * wildcard.
+
+ 2-6 instances in APRS-IS core at any given time.
+ Up to 200 invocations per second.
+ */
+
+ struct filter_refcallsign_t ref;
+ const char *e = pb->qconst_start+4;
+ int i = pb->entrycall_len;
+
+ if (i < 1) /* should not happen.. */
+ return 0; /* Bad Entry-station callsign */
+
+ /* entry station address "qA*,addr," */
+ memset( &ref, 0, sizeof(ref) ); // clear it all
+ memcpy( ref.callsign, e, i);
+
+ return filter_match_on_callsignset(&ref, i, f, MatchWild);
+}
+#endif
+
+#ifndef DISABLE_IGATE
+static int filter_process_one_f(struct pbuf_t *pb, struct filter_t *f, historydb_t *historydb)
+{
+ /* f/call/dist Friend Range filter
+
+ This is the same as the range filter except that the center is
+ defined as the last known position of call.
+
+ Multiple friend filters can be defined at the same time.
+
+ Messages addressed to stations within the range are also passed.
+ (by means of aprs packet parse finding out the location..)
+
+ NOTE: Could do static location resolving at connect time,
+ and then use the same way as 'r' range does. The friends
+ are rarely moving...
+
+ 15-25 instances in APRS-IS core at any given time.
+ Up to 900 invocations per second.
+
+ Caching the historydb_lookup() result will lower CPU power
+ spent on the historydb.
+ */
+
+ history_cell_t *history;
+
+ float r;
+ float lat1, lon1, coslat1;
+ float lat2, lon2, coslat2;
+
+ const char *callsign = f->h.u5.refcallsign.callsign;
+ int i = f->h.u5.refcallsign.reflen;
+
+ if (!(pb->flags & F_HASPOS)) { /* packet with a position.. (msgs with RECEIVER's position) */
+ if (debug) printf("f-filter: no position -> return 0\n");
+ return 0; /* No position data... */
+ }
+
+ /* find friend's last location packet */
+ if (f->h.hist_age < tick.tv_sec) {
+ history = historydb_lookup( historydb, callsign, i );
+ f->h.hist_age = tick.tv_sec + hist_lookup_interval;
+ if (!history) {
+ if (debug) printf("f-filter: no history lookup result (%*s) -> return 0\n", i, callsign );
+ return 0; /* no lookup result.. */
+ }
+ f->h.u3.numnames = 1;
+ f->h.f_latN = history->lat;
+ f->h.f_lonE = history->lon;
+ f->h.u1.f_coslat = history->coslat;
+ }
+ if (!f->h.u3.numnames) {
+ if (debug) printf("f-filter: no history lookup result (numnames == 0) -> return 0\n");
+ return 0; /* histdb lookup cache invalid */
+ }
+
+ lat1 = f->h.f_latN;
+ lon1 = f->h.f_lonE;
+ coslat1 = f->h.u1.f_coslat;
+
+ lat2 = pb->lat;
+ lon2 = pb->lng;
+ coslat2 = pb->cos_lat;
+
+ r = maidenhead_km_distance(lat1, coslat1, lon1, lat2, coslat2, lon2);
+ if (debug) printf("f-filter: r=%.1f km\n", r);
+
+ if (f->h.u2.f_dist < 0.0) {
+ // Test for _outside_ the range
+ if (r > -f->h.u2.f_dist) /* Range is more than given limit */
+ return (f->h.negation) ? 2 : 1;
+ } else {
+ // Test for _inside_ the range
+ if (r < f->h.u2.f_dist) /* Range is less than given limit */
+ return (f->h.negation) ? 2 : 1;
+ }
+
+ return 0;
+}
+#endif
+
+static int filter_process_one_g(struct pbuf_t *pb, struct filter_t *f)
+{
+ /* g/call1/call2... Group Messaging filter
+
+ Pass all message traffic TO calls call1/call2/...
+ (* wild card allowed)
+
+ */
+
+ struct filter_refcallsign_t ref;
+ int i = pb->dstname_len;
+
+ if (i > CALLSIGNLEN_MAX) i = CALLSIGNLEN_MAX;
+
+ /* source address "addr">... */
+ memset( &ref, 0, sizeof(ref) ); // clear it all
+ memcpy( ref.callsign, pb->dstname, i);
+
+ return filter_match_on_callsignset(&ref, i, f, MatchWild);
+}
+
+
+
+#if 0 // No M filter implementation, but there is M filter parse producing R filter..
+static int filter_process_one_m(struct pbuf_t *pb, struct filter_t *f)
+{
+ /* m/dist My Range filter
+
+ This is the same as the range filter except that the center is
+ defined as the last known position of the logged in client.
+
+ Messages addressed to stations within the range are also passed.
+ (by means of aprs packet parse finding out the location..)
+
+ NOTE: MY RANGE is rarely moving, once there is a positional
+ fix, it could stay fixed...
+
+ 80-120 instances in APRS-IS core at any given time.
+ Up to 4200 invocations per second.
+
+ Caching the historydb_lookup() result will lower CPU power
+ spent on the historydb.
+
+ At Aprx: Implemented using Range filter, and prepared at parse time..
+ */
+
+ float lat2, lon2, coslat2;
+ float r;
+
+ if (!(pb->flags & F_HASPOS)) /* packet with a position.. (msgs with RECEIVER's position) */
+ return 0;
+
+ lat2 = pb->lat;
+ lon2 = pb->lng;
+ coslat2 = pb->cos_lat;
+
+ r = maidenhead_km_distance(myloc_lat, myloc_coslat, myloc_lon, lat2, coslat2, lon2);
+ if (f->h.u2.f_dist < 0.0) {
+ // Test for _outside_ the range
+ if (r > -f->h.u2.f_dist) /* Range is more than given limit */
+ return (f->h.negation) ? 2 : 1;
+ } else {
+ // Test for _inside_ the range
+ if (r < f->h.u2.f_dist) /* Range is less than given limit */
+ return (f->h.negation) ? 2 : 1;
+ }
+
+ return 0;
+}
+#endif
+
+static int filter_process_one_o(struct pbuf_t *pb, struct filter_t *f)
+{
+ /* o/obj1/obj2... Object filter
+ Pass all objects with the exact name of obj1, obj2, ...
+ (* wild card allowed)
+ PROBABLY ALSO ITEMs
+
+ Usage frequency: 0.2%
+
+ .. 2 cases in entire APRS-IS core at any time.
+ About 50-70 invocations per second at peak.
+ */
+ struct filter_refcallsign_t ref;
+ int i;
+ // const char *s;
+
+ if ( (pb->packettype & (T_OBJECT|T_ITEM)) == 0 ) { /* not an Object NOR Item */
+ if (debug) printf("o-filter: packet type not OBJECT nor ITEM\n");
+ return 0;
+ }
+
+ /* parse_aprs() has picked item/object name pointer and length.. */
+ // s = pb->srcname;
+ i = pb->srcname_len;
+ if (i < 1) {
+ if (debug) printf("o-filter: object/item name length < 1 at the packet\n");
+ return 0; /* Bad object/item name */
+ }
+
+ /* object name */
+ memset( &ref, 0, sizeof(ref) ); // clear it all
+ memcpy( ref.callsign, pb->srcname, i); // copy the interesting part
+
+ return filter_match_on_callsignset(&ref, i, f, MatchWild);
+}
+
+static int filter_process_one_p(struct pbuf_t *pb, struct filter_t *f)
+{
+
+ /* p/aa/bb/cc... Prefix filter
+ Pass traffic with fromCall that start with aa or bb or cc...
+
+ Usage frequency: 14.4%
+
+ .. 80-100 cases in entire APRS-IS core at any time.
+ Up to 3500 invocations per second at peak.
+ */
+
+ struct filter_refcallsign_t ref;
+ int i = pb->srccall_end - pb->data;
+
+ if (i > CALLSIGNLEN_MAX) i = CALLSIGNLEN_MAX;
+
+ /* source address "addr">... */
+ memset( &ref, 0, sizeof(ref) ); // clear it all
+ memcpy( ref.callsign, pb->data, i);
+
+ return filter_match_on_callsignset(&ref, i, f, MatchPrefix);
+}
+
+#if 0
+static int filter_process_one_q(struct pbuf_t *pb, struct filter_t *f)
+{
+ /* q/con/ana q Contruct filter
+
+ q = q Construct command
+ con = list of q Construct to pass (case sensitive)
+ ana = analysis based on q Construct.
+
+ I = Pass positions from IGATES identified by qAr or qAR.
+
+ For example:
+ q/C Pass all traffic with qAC
+ q/rR Pass all traffic with qAr or qAR
+ q//I Pass all position packets from IGATES identified
+ in other packets by qAr or qAR
+
+ Usage frequency: 0.4%
+
+ .. 2-6 cases in entire APRS-IS core at any time.
+ Up to 200 invocations per second at peak.
+ */
+
+ const char *e = pb->qconst_start+2;
+ int mask;
+
+ switch (*e) {
+ case 'C':
+ mask = QC_C;
+ break;
+ case 'X':
+ mask = QC_X;
+ break;
+ case 'U':
+ mask = QC_U;
+ break;
+ case 'o':
+ mask = QC_o;
+ break;
+ case 'O':
+ mask = QC_O;
+ break;
+ case 'S':
+ mask = QC_S;
+ break;
+ case 'r':
+ mask = QC_r;
+ break;
+ case 'R':
+ mask = QC_R;
+ break;
+ case 'Z':
+ mask = QC_Z;
+ break;
+ case 'I':
+ mask = QC_I;
+ break;
+ default:
+ return 0; /* Should not happen... */
+ break;
+ }
+
+ if (f->h.u4.bitflags & mask) {
+ /* Something matched! */
+ return 1;
+ }
+ if (f->h.u4.bitflags & QC_AnalyticsI) {
+ /* Oh ? Analytical!
+ Has it ever been accepted into entry-igate database ? */
+ if (filter_entrycall_lookup(pb))
+ return 1; /* Found on entry-igate database! */
+ }
+
+ return 0; /* No match */
+}
+#endif
+
+static int filter_process_one_r(struct pbuf_t *pb, struct filter_t *f)
+{
+ /* r/lat/lon/dist Range filter
+
+ Pass posits and objects within dist km from lat/lon.
+ lat and lon are signed degrees, i.e. negative for West/South
+ and positive for East/North.
+
+ Multiple range filters can be defined at the same time.
+
+ Messages addressed to stations within the range are also passed.
+ (by means of aprs packet parse finding out the location..)
+
+ About 120-150 r-filters in entire APRS-IS core at any given time.
+ Up to 5200 invocations per second at peak.
+ */
+
+ float lat1 = f->h.f_latN;
+ float lon1 = f->h.f_lonE;
+ float coslat1 = f->h.u1.f_coslat;
+ float r;
+
+ float lat2, lon2, coslat2;
+
+ if (!(pb->flags & F_HASPOS)) {
+ /* packet with a position..
+ (msgs with RECEIVER's position) */
+ return 0;
+ }
+
+ lat2 = pb->lat;
+ lon2 = pb->lng;
+ coslat2 = pb->cos_lat;
+
+ r = maidenhead_km_distance(lat1, coslat1, lon1, lat2, coslat2, lon2);
+
+ if (f->h.u2.f_dist < 0.0) {
+ // Test for _outside_ the range
+ if (r > -f->h.u2.f_dist) /* Range is more than given limit */
+ return (f->h.negation) ? 2 : 1;
+ } else {
+ // Test for _inside_ the range
+ if (r < f->h.u2.f_dist) /* Range is less than given limit */
+ return (f->h.negation) ? 2 : 1;
+ }
+
+ return 0;
+}
+
+static int filter_process_one_s(struct pbuf_t *pb, struct filter_t *f)
+{
+ /* s/pri/alt/over Symbol filter
+
+ pri = symbols in primary table
+ alt = symbols in alternate table
+ over = overlay character (case sensitive)
+
+ For example:
+ s/-> This will pass all House and Car symbols (primary table)
+ s//# This will pass all Digi with or without overlay
+ s//#/T This will pass all Digi with overlay of capital T
+
+ About 10-15 s-filters in entire APRS-IS core at any given time.
+ Up to 520 invocations per second at peak.
+ */
+ const char symtable = (pb->symbol[0] == '/') ? '/' : '\\';
+ const char symcode = pb->symbol[1];
+ const char symolay = (pb->symbol[0] != symtable) ? pb->symbol[0] : 0;
+
+ // hlog( LOG_DEBUG, "s-filt %c|%c|%c %s", symtable, symcode, symolay ? symolay : '-', f->h.text );
+
+ if (f->h.u4.len1 != 0) {
+ /* Primary table symbols */
+ if ( symtable == '/' &&
+ memchr(f->h.text+f->h.u3.len1s, symcode, f->h.u4.len1) != NULL )
+ return f->h.negation ? 2 : 1;
+ // return 0;
+ }
+ if (f->h.u5.lens.len3 != 0) {
+ /* Secondary table with overlay */
+ if ( memchr(f->h.text+f->h.u5.lens.len3s, symolay, f->h.u5.lens.len3) == NULL )
+ return 0; // No match on overlay
+ if ( memchr(f->h.text+f->h.u5.lens.len2s, symcode, f->h.u5.lens.len2) == NULL )
+ return 0; // No match on overlay
+ return f->h.negation ? 2 : 1;
+ }
+ /* OK, no overlay... */
+ if (f->h.u5.lens.len2 != 0) {
+ /* Secondary table symbols */
+ if ( symtable != '/' &&
+ memchr(f->h.text+f->h.u5.lens.len2s, symcode, f->h.u5.lens.len2) != NULL )
+ return f->h.negation ? 2 : 1;
+ }
+ /* No match */
+ return 0;
+}
+
+static int filter_process_one_t(struct pbuf_t *pb, struct filter_t *f, historydb_t *historydb)
+{
+ /* [-]t/poimntqsu3*c
+ [-]t/poimntqsu3*c/call/km
+
+ Type filter Pass all traffic based on packet type.
+ One or more types can be defined at the same time, t/otq
+ is a valid definition.
+
+ c = CWOP (local extension)
+ * = ALL (local extension)
+
+ i = Items
+ m = Message
+ n = NWS Weather & Weather Objects
+ o = Objects
+ p = Position packets
+ q = Query
+ s = Status
+ t = Telemetry
+ u = User-defined
+ w = Weather
+ 3 = 3rd party frame
+
+ Note: The weather type filter also passes positions packets
+ for positionless weather packets.
+
+ The second format allows putting a radius limit around "call"
+ (station callsign-SSID or object name) for the requested station
+ types.
+
+ About 40-60 s-filters in entire APRS-IS core at any given time.
+ Up to 2100 invocations per second at peak.
+
+ For the second format perhaps 2-3 in APRS-IS at any time.
+ (mapping to 60-100 invocations per second)
+
+ Usage examples:
+
+ -t/c Everything except CWOP
+ t/.*./OH2RDY/50 Everything within 50 km of OH2RDY's last known position
+ ("." is dummy addition for C comments..)
+ */
+ int rc = 0;
+ if (pb->packettype & f->h.u4.bitflags) /* u4.bitflags as comparison bitmask */
+ rc = 1;
+#if 0
+ if (!rc && (f->h.u4.bitflags & T_WX) && (pb->flags & F_HASPOS)) {
+ /* "Note: The weather type filter also passes positions packets
+ // for positionless weather packets."
+ //
+ // 1) recognize positionless weather packets
+ // 2) register their source callsigns, do this in input_parse
+ // 3) when filtering for weather data, check non-weather
+ // recognized packets against the database in point 2
+ // 4) pass on packets matching point 3
+ */
+
+ rc = filter_wx_lookup(pb);
+ }
+#endif
+ /* Either it stops here, or it continues... */
+
+ if (rc && f->h.type == 'T') { /* Within a range of callsign ?
+ * Rather rare.. perhaps 2-3 in APRS-IS.
+ */
+ float range, r;
+ float lat1, lon1, coslat1;
+ float lat2, lon2, coslat2;
+#ifndef DISABLE_IGATE
+ const char *callsign = f->h.u5.refcallsign.callsign;
+ const int callsignlen = f->h.u5.refcallsign.reflen;
+ history_cell_t *history;
+#endif
+
+ /* hlog(LOG_DEBUG, "Type filter with callsign range used! '%s'", f->h.text); */
+
+ if (!(pb->flags & F_HASPOS)) /* packet with a position.. (msgs with RECEIVER's position) */
+ return 0; /* No positional data.. */
+
+ range = f->h.u2.f_dist;
+
+ /* So.. Now we have a callsign, and we have range.
+ Lets find callsign's location, and range to that item..
+ .. 60-100 lookups per second. */
+
+#ifndef DISABLE_IGATE
+ if (f->h.hist_age < tick.tv_sec) {
+ history = historydb_lookup( historydb, callsign, callsignlen );
+
+ /* hlog( LOG_DEBUG, "Type filter with callsign range used! call='%s', range=%.1f position %sfound",
+ // callsign, range, i ? "" : "not ");
+ */
+
+
+ if (!history) return 0; /* no lookup result.. */
+ f->h.u3.numnames = 1;
+ f->h.hist_age = tick.tv_sec + hist_lookup_interval;
+ f->h.f_latN = history->lat;
+ f->h.f_lonE = history->lon;
+ f->h.u1.f_coslat = history->coslat;
+ }
+#endif
+ if (!f->h.u3.numnames) return 0; /* No valid data at range center position cache */
+
+ lat1 = f->h.f_latN;
+ lon1 = f->h.f_lonE;
+ coslat1 = f->h.u1.f_coslat;
+
+ lat2 = pb->lat;
+ lon2 = pb->lng;
+ coslat2 = pb->cos_lat;
+
+ r = maidenhead_km_distance(lat1, coslat1, lon1, lat2, coslat2, lon2);
+
+ if (range < 0.0) {
+ // Test for _outside_ the range
+ if (r > -range) /* Range is more than given limit */
+ return (f->h.negation) ? 2 : 1;
+ } else {
+ // Test for _inside_ the range
+ if (r < range) /* Range is less than given limit */
+ return (f->h.negation) ? 2 : 1;
+ }
+
+ return 0; /* unimplemented! */
+ }
+
+ return (f->h.negation ? (rc+rc) : rc);
+}
+
+static int filter_process_one_u(struct pbuf_t *pb, struct filter_t *f)
+{
+ /* u/unproto1/unproto2/... Unproto filter
+
+ This filter passes all packets with the specified destination
+ callsign-SSID(s) (also known as the To call or unproto call).
+ Supports * wild card.
+
+ Seen hardly ever in APRS-IS core, some rare instances in Tier-2.
+ */
+
+ struct filter_refcallsign_t ref;
+ const char *d = pb->srccall_end+1;
+ int i;
+
+ i = pb->dstcall_len;
+
+ if (i > CALLSIGNLEN_MAX) i = CALLSIGNLEN_MAX;
+
+ /* hlog( LOG_INFO, "unproto: '%.*s' -> '%.*s'",
+ // (int)(pb->packet_len < 30 ? pb->packet_len : 30), pb->data, (int)i, d);
+ */
+
+ /* destination address ">addr," */
+ memset( &ref, 0, sizeof(ref) ); // clear it all
+ memcpy( ref.callsign, d, i);
+
+ return filter_match_on_callsignset(&ref, i, f, MatchWild);
+}
+
+static int filter_process_one(struct pbuf_t *pb, struct filter_t *f, historydb_t *historydb)
+{
+ int rc = 0;
+
+ if (debug>1) printf("filter_process_one() type=%c '%s'\n",f->h.type, f->h.text);
+
+ switch (f->h.type) {
+
+ case 'a':
+ case 'A':
+ rc = filter_process_one_a(pb, f);
+ break;
+
+ case 'b':
+ case 'B':
+ rc = filter_process_one_b(pb, f);
+ break;
+ case 'd':
+ case 'D':
+ rc = filter_process_one_d(pb, f);
+ break;
+
+#if 0
+ case 'e':
+ case 'E':
+ rc = filter_process_one_e(pb, f);
+ break;
+#endif
+
+#ifndef DISABLE_IGATE
+ case 'f':
+ case 'F':
+ rc = filter_process_one_f(pb, f, historydb);
+ break;
+#endif
+ case 'g':
+ case 'G':
+ rc = filter_process_one_g(pb, f);
+ break;
+
+#if 0 // these are compiled as R filters, no M filters exist internally
+ case 'm':
+ case 'M':
+ rc = filter_process_one_m(pb, f);
+ break;
+#endif
+ case 'o':
+ case 'O':
+ rc = filter_process_one_o(pb, f);
+ break;
+
+ case 'p':
+ case 'P':
+ rc = filter_process_one_p(pb, f);
+ break;
+#if 0
+ case 'q':
+ case 'Q':
+ rc = filter_process_one_q(pb, f);
+ break;
+#endif
+ case 'r':
+ case 'R':
+ rc = filter_process_one_r(pb, f);
+ break;
+
+ case 's':
+ case 'S':
+ rc = filter_process_one_s(pb, f);
+ break;
+
+ case 't':
+ case 'T':
+ rc = filter_process_one_t(pb, f, historydb);
+ break;
+
+ case 'u':
+ case 'U':
+ rc = filter_process_one_u(pb, f);
+ break;
+
+ default:
+ rc = -1;
+ break;
+ }
+ // hlog(LOG_DEBUG, "filter '%s' rc=%d", f->h.text, rc);
+
+ return rc;
+}
+
+int filter_process(struct pbuf_t *pb, struct filter_t *f, historydb_t *historydb)
+{
+ int seen_accept = 0;
+
+ for ( ; f; f = f->h.next ) {
+ int rc = filter_process_one(pb, f, historydb);
+ /* no reports to user about bad filters.. */
+ if (rc == 1)
+ seen_accept = 1;
+ else if (rc == 2)
+ return -1;
+ /* "2" reply means: "match, but don't pass.." */
+ }
+ return seen_accept;
+}
diff --git a/filter.c.2.06-to-head.diff b/filter.c.2.06-to-head.diff
new file mode 100644
index 0000000..14df525
--- /dev/null
+++ b/filter.c.2.06-to-head.diff
@@ -0,0 +1,585 @@
+Index: filter.c
+===================================================================
+--- filter.c (revision 507)
++++ filter.c (working copy)
+@@ -3,7 +3,7 @@
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+- * (c) Matti Aarnio - OH2MQK, 2007-2012 *
++ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ ********************************************************************/
+ /*
+@@ -30,6 +30,7 @@
+ d/digi1/digi2... Digipeater filter (*)
+ e/call1/call1/... Entry station filter (*)
+ f/call/dist Friend Range filter
++ g/call1/call2.. Group Messaging filter (*)
+ m/dist My Range filter
+ o/obj1/obj2... Object filter (*)
+ p/aa/bb/cc... Prefix filter
+@@ -36,8 +37,8 @@
+ q/con/ana q Contruct filter
+ r/lat/lon/dist Range filter
+ s/pri/alt/over Symbol filter
+- t/poimntqsu*c Type filter
+- t/poimntqsu*c/call/km Type filter
++ t/poimntqsu3*c Type filter
++ t/poimntqsu3*c/call/km Type filter
+ u/unproto1/unproto2/.. Unproto filter (*)
+
+ Sample usage frequencies (out of entire APRS-IS):
+@@ -338,7 +339,7 @@
+ if (f->len == keylen) {
+ int cmp = strncasecmp(f->callsign, uckey, keylen);
+ if (cmp == 0) { /* Have key match */
+- f->expirytime = now.tv_sec + filter_entrycall_maxage;
++ f->expirytime = tick.tv_sec + filter_entrycall_maxage;
+ f2 = f;
+ break;
+ }
+@@ -356,11 +357,11 @@
+ #ifndef _FOR_VALGRIND_
+ f = cellmalloc(filter_entrycall_cells);
+ #else
+- f = malloc(sizeof(*f));
++ f = calloc(1, sizeof(*f));
+ #endif
+ if (f) {
+ f->next = *fp;
+- f->expirytime = now.tv_sec + filter_entrycall_maxage;
++ f->expirytime = tick.tv_sec + filter_entrycall_maxage;
+ f->hash = hash;
+ f->len = keylen;
+ memcpy(f->callsign, uckey, keylen);
+@@ -405,7 +406,7 @@
+ int rc = strncasecmp(f->callsign, key, keylen);
+ if (rc == 0) { /* Have key match, see if it is
+ still valid entry ? */
+- if (f->expirytime < now.tv_sec - 60) {
++ if ((f->expirytime - (tick.tv_sec - 60)) < 0) {
+ f2 = f;
+ break;
+ }
+@@ -432,7 +433,7 @@
+ fp = & filter_entrycall_hash[k];
+ while (( f = *fp )) {
+ /* Did it expire ? */
+- if (f->expirytime <= now.tv_sec) {
++ if ((f->expirytime - tick.tv_sec) <= 0) {
+ *fp = f->next;
+ f->next = NULL;
+ filter_entrycall_free(f);
+@@ -539,7 +540,7 @@
+ if (f->len == keylen) {
+ int cmp = memcmp(f->callsign, uckey, keylen);
+ if (cmp == 0) { /* Have key match */
+- f->expirytime = now.tv_sec + filter_wx_maxage;
++ f->expirytime = tick.tv_sec + filter_wx_maxage;
+ f2 = f;
+ break;
+ }
+@@ -557,12 +558,12 @@
+ #ifndef _FOR_VALGRIND_
+ f = cellmalloc(filter_wx_cells);
+ #else
+- f = malloc(sizeof(*f));
++ f = calloc(1, sizeof(*f));
+ #endif
+ ++filter_wx_cellgauge;
+ if (f) {
+ f->next = *fp;
+- f->expirytime = now.tv_sec + filter_wx_maxage;
++ f->expirytime = tick.tv_sec + filter_wx_maxage;
+ f->hash = hash;
+ f->len = keylen;
+ memcpy(f->callsign, key, keylen);
+@@ -593,7 +594,7 @@
+ int rc = strncasecmp(f->callsign, key, keylen);
+ if (rc == 0) { /* Have key match, see if it is
+ still valid entry ? */
+- if (f->expirytime < now.tv_sec - 60) {
++ if ((f->expirytime - (tick.tv_sec - 60)) < 0) {
+ f2 = f;
+ break;
+ }
+@@ -621,7 +622,7 @@
+ fp = & filter_wx_hash[k];
+ while (( f = *fp )) {
+ /* Did it expire ? */
+- if (f->expirytime <= now.tv_sec) {
++ if ((f->expirytime - tick.tv_sec) <= 0) {
+ *fp = f->next;
+ f->next = NULL;
+ filter_wx_free(f);
+@@ -804,13 +805,27 @@
+ /* count the number of prefixes in there.. */
+ while (*p) {
+ if (*p) ++refmax;
+- while (*p && *p != '/' && *p != ',') ++p;
+- if (*p == '/' || *p == ',') ++p;
++ while (*p && *p != '/') ++p;
++ if (*p == '/') ++p;
+ }
+- if (refmax == 0) return -1; /* No prefixes ?? */
++ if (refmax == 0) {
++ printf("Filter definition of '%s' has no prefixes defined.\n", filt0);
++ return -1; /* No prefixes ?? */
++ }
+
+- refbuf = malloc(sizeof(*refbuf)*refmax);
+- refcount = 0;
++ if (ff && ff->h.type == f0->h.type) { /* SAME TYPE,
++ extend previous record! */
++ extend = 1;
++ refcount = ff->h.u3.numnames + refmax;
++ refbuf = realloc(ff->h.u5.refcallsigns, sizeof(*refbuf) * refcount);
++ ff->h.u5.refcallsigns = refbuf;
++ refcount = ff->h.u3.numnames;
++ } else {
++ refbuf = calloc(1, sizeof(*refbuf)*refmax);
++ refcount = 0;
++ f0->h.u5.refcallsigns = refbuf;
++ f0->h.u3.numnames = 0;
++ }
+
+ p = filt0;
+ if (*p == '-') ++p;
+@@ -824,24 +839,28 @@
+ memset(prefixbuf, 0, sizeof(prefixbuf));
+ i = 0;
+ wildcard = 0;
+- while (*p != 0 && *p != '/' && *p != ',') {
++ while (*p != 0 && *p != '/') {
+ int c = *p++;
+ if (c == '*') {
+- wildcard = 1;
+- if (wildok != MatchWild)
+- return -1;
+- continue;
++ wildcard = 1;
++ if (wildok != MatchWild) {
++ printf("Wild-card matching not permitted, yet filter definition says: '%s'\n", filt0);
++ return -1;
++ }
++ continue;
+ }
+- ++i;
+- if (i < (CALLSIGNLEN_MAX)) {
+- *k = c;
+- ++k;
++ if (i < CALLSIGNLEN_MAX) {
++ *k++ = c;
++ ++i;
++ } else {
++ printf("Too long callsign string: '%s' input: '%s'\n", prefixbuf, filt0);
++ return -1; // invalid input
+ }
+ }
+ *k = 0;
+ /* OK, we have one prefix part collected, scan source until next '/' */
+- if (*p != 0 && *p != '/' && *p != ',') ++p;
+- if (*p == '/' || *p == ',') ++p;
++ if (*p != 0 && *p != '/') ++p;
++ if (*p == '/') ++p;
+ /* If there is more of patterns, the loop continues.. */
+
+ /* Store the refprefix */
+@@ -855,11 +874,10 @@
+ ++refcount;
+ }
+
+- f0->h.u5.refcallsigns = refbuf;
+- f0->h.u3.numnames = refcount;
++ f0->h.u3.numnames = refcount;
+ if (extend) {
+ char *s;
+- ff->h.u3.numnames = refcount;
++ ff->h.u3.numnames = refcount;
+ i = strlen(ff->h.text) + strlen(filt0)+2;
+ if (i <= FILT_TEXTBUFSIZE) {
+ /* Fits in our built-in buffer block - like previous..
+@@ -1081,7 +1099,6 @@
+
+
+ break;
+-#if 0
+ case 'd':
+ case 'D':
+ /* d/digi1/digi2... Digipeater filter (*) */
+@@ -1093,7 +1110,6 @@
+ return 0;
+
+ break;
+-#endif
+ #if 0
+ case 'e':
+ case 'E':
+@@ -1133,11 +1149,31 @@
+ */
+
+ break;
+-#if 0
++
++ case 'g':
++ case 'G':
++ // g/call1/call2/ Group Messaging filter
++ i = filter_parse_one_callsignset(ffp, &f0, filt0, MatchWild );
++ if (i < 0)
++ return i;
++ if (i > 0) /* extended previous */
++ return 0;
++ break;
++
+ case 'm':
+ case 'M':
+ /* m/dist My range filter */
+
++ if (myloc_latstr == NULL) {
++ printf("The M/radius_km filter requires top-level myloc definition. It doesn't exist.\n");
++ return -1;
++ }
++
++ f0.h.type = 'r'; // internal implementation at Aprx is a RANGE filter.
++ f0.h.f_latN = myloc_lat; // radians
++ f0.h.f_lonE = myloc_lon; // radians
++ f0.h.u1.f_coslat = myloc_coslat;
++
+ i = sscanf(filt+1, "/%f", &f0.h.u2.f_dist);
+ if (i != 1 || f0.h.u2.f_dist < 0.1) {
+ // hlog(LOG_DEBUG, "Bad filter parse: %s", filt0);
+@@ -1149,7 +1185,7 @@
+
+ // hlog(LOG_DEBUG, "Filter: %s -> M %.3f", filt0, f0.h.u2.f_dist);
+ break;
+-#endif
++
+ case 'o':
+ case 'O':
+ /* o/obje1/obj2... Object filter (*) */
+@@ -1315,6 +1351,9 @@
+ case '*':
+ f0.h.u4.bitflags |= ~T_CWOP; /* "ALL" -- excluding CWOP */
+ break;
++ case '3':
++ f0.h.u4.bitflags |= T_THIRDPARTY;
++ break;
+ case 'c': case 'C':
+ f0.h.u4.bitflags |= T_CWOP;
+ break;
+@@ -1409,7 +1448,7 @@
+ } else
+ f->h.text = strdup(filt0); /* and copy of filter text */
+ #else
+- f = malloc(sizeof(*f) + strlen(filt0));
++ f = calloc(1, sizeof(*f) + strlen(filt0));
+ *f = f0; /* store pre-parsed values */
+ f->h.text = f->textbuf;
+ strcpy(f->textbuf, filt); /* and copy of filter text */
+@@ -1556,22 +1595,21 @@
+ if (i > CALLSIGNLEN_MAX) i = CALLSIGNLEN_MAX;
+
+ /* source address "addr">... */
+- memset( ref.callsign, 0, sizeof(ref.callsign));
++ memset( &ref, 0, sizeof(ref) ); // clear it all
+ memcpy( ref.callsign, pb->data, i);
+
+ return filter_match_on_callsignset(&ref, i, f, MatchWild);
+ }
+
+-#if 0
+ static int filter_process_one_d(struct pbuf_t *pb, struct filter_t *f)
+ {
+ /* d/digi1/digi2... Digipeater filter
+
+- The digipeater filter will pass all packets that have been
++ The digipeater filter will match all packets that have been
+ digipeated by a particular station(s) (the station's call
+ is in the path). This filter allows the * wildcard.
+
+- 25-35 filters in use at any given time.
++ 25-35 instances in APRS-IS core at any given time.
+ Up to 1300 invocations per second.
+ */
+ struct filter_refcallsign_t ref;
+@@ -1585,9 +1623,9 @@
+
+ for (i = 0; d < q; ) {
+ ++j;
+- if (j > 10) break; /* way too many callsigns... */
++ if (j > 10) break; // way too many callsigns... (code bug?)
+
+- if (*d == ',') ++d; /* second round and onwards.. */
++ if (*d == ',') ++d; // second round and onwards..
+ for (i = 0; i+d <= q && i <= CALLSIGNLEN_MAX; ++i) {
+ if (d[i] == ',')
+ break;
+@@ -1595,9 +1633,9 @@
+
+ // hlog(LOG_INFO, "d: -> (%d,%d) '%.*s'", (int)(d-pb->data), i, i, d);
+
+- /* digipeater address ",addr," */
++ // digipeater address ",addr,"
++ memset( &ref, 0, sizeof(ref) ); // clear it all
+ memcpy( ref.callsign, d, i);
+- memset( ref.callsign+i, 0, sizeof(ref)-i );
+
+ if (i > CALLSIGNLEN_MAX) i = CALLSIGNLEN_MAX;
+
+@@ -1609,7 +1647,6 @@
+ }
+ return 0;
+ }
+-#endif
+
+ #if 0
+ static int filter_process_one_e(struct pbuf_t *pb, struct filter_t *f)
+@@ -1616,7 +1653,7 @@
+ {
+ /* e/call1/call1/... Entry station filter
+
+- This filter passes all packets with the specified
++ This filter matches all packets with the specified
+ callsign-SSID(s) immediately following the q construct.
+ This allows filtering based on receiving IGate, etc.
+ Supports * wildcard.
+@@ -1633,8 +1670,8 @@
+ return 0; /* Bad Entry-station callsign */
+
+ /* entry station address "qA*,addr," */
++ memset( &ref, 0, sizeof(ref) ); // clear it all
+ memcpy( ref.callsign, e, i);
+- memset( ref.callsign+i, 0, sizeof(ref)-i );
+
+ return filter_match_on_callsignset(&ref, i, f, MatchWild);
+ }
+@@ -1644,6 +1681,7 @@
+ static int filter_process_one_f(struct pbuf_t *pb, struct filter_t *f, historydb_t *historydb)
+ {
+ /* f/call/dist Friend Range filter
++
+ This is the same as the range filter except that the center is
+ defined as the last known position of call.
+
+@@ -1678,9 +1716,9 @@
+ }
+
+ /* find friend's last location packet */
+- if (f->h.hist_age < now.tv_sec) {
++ if (f->h.hist_age < tick.tv_sec) {
+ history = historydb_lookup( historydb, callsign, i );
+- f->h.hist_age = now.tv_sec + hist_lookup_interval;
++ f->h.hist_age = tick.tv_sec + hist_lookup_interval;
+ if (!history) {
+ if (debug) printf("f-filter: no history lookup result (%*s) -> return 0\n", i, callsign );
+ return 0; /* no lookup result.. */
+@@ -1720,10 +1758,34 @@
+ }
+ #endif
+
+-#if 0
++static int filter_process_one_g(struct pbuf_t *pb, struct filter_t *f)
++{
++ /* g/call1/call2... Group Messaging filter
++
++ Pass all message traffic TO calls call1/call2/...
++ (* wild card allowed)
++
++ */
++
++ struct filter_refcallsign_t ref;
++ int i = pb->dstname_len;
++
++ if (i > CALLSIGNLEN_MAX) i = CALLSIGNLEN_MAX;
++
++ /* source address "addr">... */
++ memset( &ref, 0, sizeof(ref) ); // clear it all
++ memcpy( ref.callsign, pb->dstname, i);
++
++ return filter_match_on_callsignset(&ref, i, f, MatchWild);
++}
++
++
++
++#if 0 // No M filter implementation, but there is M filter parse producing R filter..
+ static int filter_process_one_m(struct pbuf_t *pb, struct filter_t *f)
+ {
+ /* m/dist My Range filter
++
+ This is the same as the range filter except that the center is
+ defined as the last known position of the logged in client.
+
+@@ -1738,40 +1800,21 @@
+
+ Caching the historydb_lookup() result will lower CPU power
+ spent on the historydb.
++
++ At Aprx: Implemented using Range filter, and prepared at parse time..
+ */
+
+- float lat1, lon1, coslat1;
+ float lat2, lon2, coslat2;
+ float r;
+- history_cell_t *history;
+
+-
+ if (!(pb->flags & F_HASPOS)) /* packet with a position.. (msgs with RECEIVER's position) */
+ return 0;
+
+- if (!c->username) /* Should not happen... */
+- return 0;
+-
+- if (f->h.hist_age < now.tv_sec) {
+- history = historydb_lookup( c->username, strlen(c->username) );
+- f->h.hist_age = now.tv_sec + hist_lookup_interval;
+- if (!history) return 0; /* no result */
+- f->h.u3.numnames = 1;
+- f->h.f_latN = history->lat;
+- f->h.f_lonE = history->lon;
+- f->h.u1.f_coslat = history->coslat;
+- }
+- if (!f->h.u3.numnames) return 0; /* cached lookup invalid.. */
+-
+- lat1 = f->h.f_latN;
+- lon1 = f->h.f_lonE;
+- coslat1 = f->h.u1.f_coslat;
+-
+ lat2 = pb->lat;
+ lon2 = pb->lng;
+ coslat2 = pb->cos_lat;
+
+- r = maidenhead_km_distance(lat1, coslat1, lon1, lat2, coslat2, lon2);
++ r = maidenhead_km_distance(myloc_lat, myloc_coslat, myloc_lon, lat2, coslat2, lon2);
+ if (f->h.u2.f_dist < 0.0) {
+ // Test for _outside_ the range
+ if (r > -f->h.u2.f_dist) /* Range is more than given limit */
+@@ -1785,6 +1828,7 @@
+ return 0;
+ }
+ #endif
++
+ static int filter_process_one_o(struct pbuf_t *pb, struct filter_t *f)
+ {
+ /* o/obj1/obj2... Object filter
+@@ -1801,17 +1845,22 @@
+ int i;
+ // const char *s;
+
+- if ( (pb->packettype & (T_OBJECT|T_ITEM)) == 0 ) /* not an Object NOR Item */
++ if ( (pb->packettype & (T_OBJECT|T_ITEM)) == 0 ) { /* not an Object NOR Item */
++ if (debug) printf("o-filter: packet type not OBJECT nor ITEM\n");
+ return 0;
++ }
+
+ /* parse_aprs() has picked item/object name pointer and length.. */
+ // s = pb->srcname;
+ i = pb->srcname_len;
+- if (i < 1) return 0; /* Bad object/item name */
++ if (i < 1) {
++ if (debug) printf("o-filter: object/item name length < 1 at the packet\n");
++ return 0; /* Bad object/item name */
++ }
+
+ /* object name */
+- memcpy( ref.callsign, pb->info_start+1, i);
+- memset( ref.callsign+i, 0, sizeof(ref)-i );
++ memset( &ref, 0, sizeof(ref) ); // clear it all
++ memcpy( ref.callsign, pb->srcname, i); // copy the interesting part
+
+ return filter_match_on_callsignset(&ref, i, f, MatchWild);
+ }
+@@ -1834,8 +1883,8 @@
+ if (i > CALLSIGNLEN_MAX) i = CALLSIGNLEN_MAX;
+
+ /* source address "addr">... */
++ memset( &ref, 0, sizeof(ref) ); // clear it all
+ memcpy( ref.callsign, pb->data, i);
+- memset( ref.callsign+i, 0, sizeof(ref)-i );
+
+ return filter_match_on_callsignset(&ref, i, f, MatchPrefix);
+ }
+@@ -2006,7 +2055,7 @@
+ /* OK, no overlay... */
+ if (f->h.u5.lens.len2 != 0) {
+ /* Secondary table symbols */
+- if ( symtable != '\\' &&
++ if ( symtable != '/' &&
+ memchr(f->h.text+f->h.u5.lens.len2s, symcode, f->h.u5.lens.len2) != NULL )
+ return f->h.negation ? 2 : 1;
+ }
+@@ -2016,8 +2065,8 @@
+
+ static int filter_process_one_t(struct pbuf_t *pb, struct filter_t *f, historydb_t *historydb)
+ {
+- /* [-]t/poimntqsu
+- [-]t/poimntqsu/call/km
++ /* [-]t/poimntqsu3*c
++ [-]t/poimntqsu3*c/call/km
+
+ Type filter Pass all traffic based on packet type.
+ One or more types can be defined at the same time, t/otq
+@@ -2036,6 +2085,7 @@
+ t = Telemetry
+ u = User-defined
+ w = Weather
++ 3 = 3rd party frame
+
+ Note: The weather type filter also passes positions packets
+ for positionless weather packets.
+@@ -2100,7 +2150,7 @@
+ .. 60-100 lookups per second. */
+
+ #ifndef DISABLE_IGATE
+- if (f->h.hist_age < now.tv_sec) {
++ if (f->h.hist_age < tick.tv_sec) {
+ history = historydb_lookup( historydb, callsign, callsignlen );
+
+ /* hlog( LOG_DEBUG, "Type filter with callsign range used! call='%s', range=%.1f position %sfound",
+@@ -2110,7 +2160,7 @@
+
+ if (!history) return 0; /* no lookup result.. */
+ f->h.u3.numnames = 1;
+- f->h.hist_age = now.tv_sec + hist_lookup_interval;
++ f->h.hist_age = tick.tv_sec + hist_lookup_interval;
+ f->h.f_latN = history->lat;
+ f->h.f_lonE = history->lon;
+ f->h.u1.f_coslat = history->coslat;
+@@ -2168,8 +2218,8 @@
+ */
+
+ /* destination address ">addr," */
++ memset( &ref, 0, sizeof(ref) ); // clear it all
+ memcpy( ref.callsign, d, i);
+- memset( ref.callsign+i, 0, sizeof(ref)-i );
+
+ return filter_match_on_callsignset(&ref, i, f, MatchWild);
+ }
+@@ -2191,12 +2241,12 @@
+ case 'B':
+ rc = filter_process_one_b(pb, f);
+ break;
+-#if 0
+ case 'd':
+ case 'D':
+ rc = filter_process_one_d(pb, f);
+ break;
+
++#if 0
+ case 'e':
+ case 'E':
+ rc = filter_process_one_e(pb, f);
+@@ -2209,7 +2259,12 @@
+ rc = filter_process_one_f(pb, f, historydb);
+ break;
+ #endif
+-#if 0
++ case 'g':
++ case 'G':
++ rc = filter_process_one_g(pb, f);
++ break;
++
++#if 0 // these are compiled as R filters, no M filters exist internally
+ case 'm':
+ case 'M':
+ rc = filter_process_one_m(pb, f);
diff --git a/historydb.c b/historydb.c
new file mode 100644
index 0000000..9952863
--- /dev/null
+++ b/historydb.c
@@ -0,0 +1,631 @@
+/********************************************************************
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ ********************************************************************/
+
+#include "aprx.h"
+
+#ifndef DISABLE_IGATE
+
+#include <strings.h>
+#include <ctype.h>
+#include <math.h>
+
+
+int lastposition_storetime = 3600; // 1 hour
+
+static historydb_t **_dbs;
+static int _dbs_count;
+
+void historydb_nopos(void) {} /* profiler call counter items */
+void historydb_nointerest(void) {}
+void historydb_hashmatch(void) {}
+void historydb_keymatch(void) {}
+void historydb_dataupdate(void) {}
+
+// Single aprx wide alloc system
+static cellarena_t *historydb_cells;
+
+const int historydb_cellsize = sizeof(struct history_cell_t);
+const int historydb_cellalign = __alignof__(struct history_cell_t);
+
+void historydb_init(void)
+{
+ // printf("historydb_init() sizeof(mutex)=%d sizeof(rwlock)=%d\n",
+ // sizeof(pthread_mutex_t), sizeof(rwlock_t));
+
+ // _dbs = malloc(sizeof(void*));
+ // _dbs_count = 0;
+
+ historydb_cells = cellinit( "historydb",
+ historydb_cellsize,
+ historydb_cellalign,
+ CELLMALLOC_POLICY_FIFO,
+ 32 /* 32 kB */,
+ 0 /* minfree */ );
+}
+
+/* new instance - for new digipeater tx */
+historydb_t *historydb_new(void)
+{
+ historydb_t *db = calloc(1, sizeof(*db));
+
+ ++_dbs_count;
+ _dbs = realloc(_dbs, sizeof(void*)*_dbs_count);
+ _dbs[_dbs_count-1] = db;
+
+ return db;
+}
+
+
+
+/* Called only under WR-LOCK */
+void historydb_free(struct history_cell_t *p)
+{
+ if (p->packet != p->packetbuf)
+ free(p->packet);
+ if (p->last_heard != p->last_heard_buf)
+ free(p->last_heard);
+
+ --p->db->historydb_cellgauge;
+
+ cellfree( historydb_cells, p );
+}
+
+/* Called only under WR-LOCK */
+struct history_cell_t *historydb_alloc(historydb_t *db, int packet_len)
+{
+ struct history_cell_t *ret = cellmalloc( historydb_cells );
+ if (!ret) return NULL;
+ ++db->historydb_cellgauge;
+ ret->db = db;
+ ret->last_heard = ((top_interfaces_group <= MAX_IF_GROUP) ?
+ ret->last_heard_buf :
+ malloc(sizeof(time_t)*top_interfaces_group));
+ return ret;
+}
+
+/*
+ * The historydb_atend() does exist primarily to make valgrind
+ * happy about lost memory object tracking.
+ */
+void historydb_atend(void)
+{
+ int j, i;
+ for (j = 0; j < _dbs_count; ++j) {
+ historydb_t *db = _dbs[j];
+ struct history_cell_t *hp, *hp2;
+ for (i = 0; i < HISTORYDB_HASH_MODULO; ++i) {
+ hp = db->hash[i];
+ while (hp) {
+ hp2 = hp->next;
+ historydb_free(hp);
+ hp = hp2;
+ }
+ }
+ }
+}
+
+void historydb_dump_entry(FILE *fp, const struct history_cell_t *hp)
+{
+ fprintf(fp, "%ld\t", hp->arrivaltime);
+ (void)fwrite(hp->key, hp->keylen, 1, fp);
+ fprintf(fp, "\t");
+ fprintf(fp, "%d\t%d\t", hp->packettype, hp->flags);
+ fprintf(fp, "%f\t%f\t", hp->lat, hp->lon);
+ fprintf(fp, "%d\t", hp->packetlen);
+ (void)fwrite(hp->packet, hp->packetlen, 1, fp);
+ fprintf(fp, "\n"); /* newline */
+}
+
+void historydb_dump(const historydb_t *db, FILE *fp)
+{
+ /* Dump the historydb out on text format */
+ int i;
+ struct history_cell_t *hp;
+ time_t expirytime = tick.tv_sec - lastposition_storetime;
+
+ for ( i = 0; i < HISTORYDB_HASH_MODULO; ++i ) {
+ hp = db->hash[i];
+ for ( ; hp ; hp = hp->next )
+ if (timecmp(hp->arrivaltime, expirytime) > 0)
+ historydb_dump_entry(fp, hp);
+ }
+}
+
+
+static int foldhash( const unsigned int h1 )
+{
+ unsigned int h2 = h1 ^ (h1 >> 7) ^ (h1 >> 14); /* fold hash bits.. */
+ return (h2 % HISTORYDB_HASH_MODULO);
+}
+
+
+/* insert... */
+
+history_cell_t *historydb_insert(historydb_t *db, const struct pbuf_t *pb)
+{
+ return historydb_insert_(db, pb, 0);
+}
+
+history_cell_t *historydb_insert_(historydb_t *db, const struct pbuf_t *pb, const int insertall)
+{
+ int i;
+ unsigned int h1;
+ int isdead = 0, keylen;
+ struct history_cell_t **hp, *cp, *cp1;
+
+ time_t expirytime = tick.tv_sec - lastposition_storetime;
+
+ char keybuf[CALLSIGNLEN_MAX+2];
+ char *s;
+
+ // (pb->flags & F_HASPOS) <-- that indicates that at parse time
+ // the packet either had a position, or
+ // a position information was supplemented
+ // to it via historydb lookup
+
+ if (!insertall && !(pb->packettype & T_POSITION)) { // <-- packet has position data
+ ++db->historydb_noposcount;
+ historydb_nopos(); /* debug thing -- profiling counter */
+ return NULL; /* No positional data... */
+ }
+
+ /* NOTE: Parser does set on MESSAGES the RECIPIENTS
+ ** location if such is known! We do not want them...
+ ** .. and several other cases where packet has no
+ ** positional data in it, but source callsign may
+ ** have previous entry with data.
+ */
+
+ /* NOTE2: We could use pb->srcname, and pb->srcname_len here,
+ ** but then we would not know if this is a "kill-item"
+ */
+
+ keybuf[CALLSIGNLEN_MAX] = 0;
+ if (pb->packettype & T_OBJECT) {
+ /* Pick object name ";item *" */
+ memcpy( keybuf, pb->info_start+1, CALLSIGNLEN_MAX+1);
+ keybuf[CALLSIGNLEN_MAX+1] = 0;
+ s = strchr(keybuf, '*');
+ if (s) *s = 0;
+ else {
+ s = strchr(keybuf, '_'); // kill an object!
+ if (s) {
+ *s = 0;
+ isdead = 1;
+ }
+ }
+ s = keybuf + strlen(keybuf);
+ for ( ; s > keybuf; --s ) { // tail space padded..
+ if (*s == ' ') *s = ' ';
+ else break;
+ }
+
+ } else if (pb->packettype & T_ITEM) {
+ // Pick item name ") . . . !" or ") . . . _"
+ memcpy( keybuf, pb->info_start+1, CALLSIGNLEN_MAX+1);
+ keybuf[CALLSIGNLEN_MAX+1] = 0;
+ s = strchr(keybuf, '!');
+ if (s) *s = 0;
+ else {
+ s = strchr(keybuf, '_'); // kill an item!
+ if (s) {
+ *s = 0;
+ isdead = 1;
+ }
+ }
+
+ } else if (pb->packettype & T_MESSAGE) {
+ // Pick originator callsign
+ memcpy( keybuf, pb->data, CALLSIGNLEN_MAX) ;
+ s = strchr(keybuf, '>');
+ if (s) *s = 0;
+
+ } else if (pb->packettype & T_POSITION) {
+ // Pick originator callsign
+ memcpy( keybuf, pb->data, CALLSIGNLEN_MAX) ;
+ s = strchr(keybuf, '>');
+ if (s) *s = 0;
+
+ } else {
+ if (insertall) {
+ // Pick originator callsign
+ memcpy( keybuf, pb->data, CALLSIGNLEN_MAX) ;
+ s = strchr(keybuf, '>');
+ if (s) *s = 0;
+
+ } else {
+
+ historydb_nointerest(); // debug thing -- a profiling counter
+ return NULL; // Not a packet with positional data, not interested in...
+ }
+ }
+ keylen = strlen(keybuf);
+
+ ++db->historydb_inserts;
+
+ h1 = keyhash(keybuf, keylen, 0);
+ i = foldhash(h1);
+ if (debug > 1) printf(" key='%s' hash=%d", keybuf, i);
+
+ cp = cp1 = NULL;
+ hp = &db->hash[i];
+
+ // scan the hash-bucket chain, and do incidential obsolete data discard
+ while (( cp = *hp )) {
+ if (timecmp(cp->arrivaltime, expirytime) < 0) {
+ // OLD...
+ *hp = cp->next;
+ cp->next = NULL;
+ historydb_free(cp);
+ continue;
+ }
+ if ( (cp->hash1 == h1)) {
+ // Hash match, compare the key
+ historydb_hashmatch(); // debug thing -- a profiling counter
+ ++db->historydb_hashmatches;
+ if ( cp->keylen == keylen &&
+ (memcmp(cp->key, keybuf, keylen) == 0) ) {
+ // Key match!
+ historydb_keymatch(); // debug thing -- a profiling counter
+ ++db->historydb_keymatches;
+ if (isdead) {
+ // Remove this key..
+ *hp = cp->next;
+ cp->next = NULL;
+ historydb_free(cp);
+ continue;
+ } else {
+ historydb_dataupdate(); // debug thing -- a profiling counter
+ // Update the data content
+ cp1 = cp;
+ if (pb->flags & F_HASPOS) {
+ // Update coordinate, if available
+ cp->lat = pb->lat;
+ cp->coslat = pb->cos_lat;
+ cp->lon = pb->lng;
+ cp->positiontime = pb->t;
+ }
+ cp->packettype = pb->packettype;
+ cp->flags |= pb->flags;
+
+ cp->arrivaltime = pb->t;
+ cp->flags = pb->flags;
+ cp->packetlen = pb->packet_len;
+ cp->last_heard[pb->source_if_group] = pb->t;
+ if ( cp->packet != cp->packetbuf )
+ free( cp->packet );
+
+ cp->packet = cp->packetbuf; // default case
+ if ( cp->packetlen > sizeof(cp->packetbuf) ) {
+ // Needs bigger buffer than pre-allocated one,
+ // thus it retrieves that one from heap.
+ cp->packet = malloc( cp->packetlen );
+ }
+ memcpy( cp->packet, pb->data, cp->packetlen );
+ }
+ }
+ } // .. else no match, advance hp..
+ hp = &(cp -> next);
+ }
+
+ if (!cp1 && !isdead) {
+ // Not found on this chain, append it!
+ cp = historydb_alloc(db, pb->packet_len);
+ cp->next = NULL;
+ memcpy(cp->key, keybuf, keylen);
+ cp->key[keylen] = 0; /* zero terminate */
+ cp->keylen = keylen;
+ cp->hash1 = h1;
+
+ cp->lat = pb->lat;
+ cp->coslat = pb->cos_lat;
+ cp->lon = pb->lng;
+ cp->arrivaltime = pb->t;
+ cp->packettype = pb->packettype;
+ cp->flags = pb->flags;
+ cp->last_heard[pb->source_if_group] = pb->t;
+ if (pb->flags & F_HASPOS)
+ cp->positiontime = pb->t;
+
+ cp->packetlen = pb->packet_len;
+ cp->packet = cp->packetbuf; // default case
+ if (cp->packetlen > sizeof(cp->packetbuf)) {
+ // Needs bigger buffer than pre-allocated one,
+ // thus it retrieves that one from heap.
+ cp->packet = malloc( cp->packetlen );
+ }
+
+ // Initial value is 32.0 tokens to permit
+ // digipeat a packet source at the first
+ // time it has been heard -- including to
+ // possible multiple transmitters. Within
+ // about 5 seconds this will be dropped
+ // down to max burst rate of the srcratefilter
+ // parameter. This code does not know how
+ // many interfaces there are...
+ cp->tokenbucket = 32.0;
+
+ *hp = cp;
+ }
+
+ return *hp;
+}
+
+history_cell_t *historydb_insert_heard(historydb_t *db, const struct pbuf_t *pb)
+{
+ int i;
+ unsigned int h1;
+ int keylen;
+ struct history_cell_t **hp, *cp, *cp1;
+
+ time_t expirytime = tick.tv_sec - lastposition_storetime;
+
+ char keybuf[CALLSIGNLEN_MAX+2];
+ char *s;
+
+
+ /* NOTE: Parser does set on MESSAGES the RECIPIENTS
+ ** location if such is known! We do not want them...
+ ** .. and several other cases where packet has no
+ ** positional data in it, but source callsign may
+ ** have previous entry with data.
+ */
+
+ /* NOTE2: We could use pb->srcname, and pb->srcname_len here,
+ ** but then we would not know if this is a "kill-item"
+ */
+
+ keybuf[CALLSIGNLEN_MAX+1] = 0;
+
+ if (pb->packettype & T_OBJECT) {
+ historydb_nointerest(); // debug thing -- a profiling counter
+ if (debug > 1) printf(" .. objects not interested\n");
+ return NULL; // Not interested in ";objects :"
+
+ } else if (pb->packettype & T_ITEM) {
+ historydb_nointerest(); // debug thing -- a profiling counter
+ if (debug > 1) printf(" .. items not interested\n");
+ return NULL; // Not interested in ")items..."
+
+ } else if (pb->packettype & T_MESSAGE) {
+ // Pick originator callsign
+ //memcpy( keybuf, pb->data, CALLSIGNLEN_MAX) ;
+ //s = strchr(keybuf, '>');
+ //if (s) *s = 0;
+ memcpy(keybuf, pb->srcname, pb->srcname_len);
+ keybuf[pb->srcname_len] = 0;
+
+ } else if (pb->packettype & T_POSITION) {
+ // Something with a position (but not an item or an object)
+ //memcpy( keybuf, pb->data, CALLSIGNLEN_MAX) ;
+ //s = strchr(keybuf, '>');
+ //if (s) *s = 0;
+ memcpy(keybuf, pb->srcname, pb->srcname_len);
+ keybuf[pb->srcname_len] = 0;
+
+ } else {
+ if (debug > 1) printf(" .. other not interested\n");
+ historydb_nointerest(); // debug thing -- a profiling counter
+ return NULL; // Not a packet with positional data, not interested in...
+ }
+ keylen = strlen(keybuf);
+
+ ++db->historydb_inserts;
+
+ h1 = keyhash(keybuf, keylen, 0);
+ i = foldhash(h1);
+ if (debug > 1) printf(" key='%s' hash=%d", keybuf, i);
+
+ cp1 = NULL;
+ hp = &db->hash[i];
+
+ // scan the hash-bucket chain, and do incidential obsolete data discard
+ while (( cp = *hp ) != NULL) {
+ if (timecmp(cp->arrivaltime, expirytime) < 0) {
+ // OLD...
+ if (debug > 1) printf(" .. dropping old record\n");
+ *hp = cp->next;
+ cp->next = NULL;
+ historydb_free(cp);
+ continue;
+ }
+ if ( (cp->hash1 == h1)) {
+ // Hash match, compare the key
+ historydb_hashmatch(); // debug thing -- a profiling counter
+ ++db->historydb_hashmatches;
+ if (debug > 1) printf(" .. found matching hash");
+ if ( cp->keylen == keylen &&
+ (memcmp(cp->key, keybuf, keylen) == 0) ) {
+ // Key match!
+ if (debug > 1) printf(" .. found matching key!\n");
+
+ historydb_keymatch(); // debug thing -- a profiling counter
+ ++db->historydb_keymatches;
+
+ historydb_dataupdate(); // debug thing -- a profiling counter
+ // Update the data content
+ cp1 = cp;
+ if (pb->flags & F_HASPOS) {
+ // Update coordinate, if available
+ cp->lat = pb->lat;
+ cp->coslat = pb->cos_lat;
+ cp->lon = pb->lng;
+ cp->positiontime = pb->t;
+ cp->arrivaltime = pb->t;
+ }
+ cp->flags |= pb->flags;
+
+ // Track packet source timestamps
+ cp->last_heard[pb->source_if_group] = pb->t;
+
+ // Don't save a message on top of positional packet
+ if (!(pb->packettype & T_MESSAGE)) {
+ cp->packettype = pb->packettype;
+ cp->arrivaltime = pb->t;
+ cp->flags = pb->flags;
+ cp->packetlen = pb->packet_len;
+ if ( cp->packet != cp->packetbuf )
+ free( cp->packet );
+
+ cp->packet = cp->packetbuf; // default case
+ if ( cp->packetlen > sizeof(cp->packetbuf) ) {
+ // Needs bigger buffer than pre-allocated one,
+ // thus it retrieves that one from heap.
+ cp->packet = malloc( cp->packetlen );
+ }
+ memcpy( cp->packet, pb->data, cp->packetlen );
+ }
+ }
+ } // .. else no match, advance hp..
+ hp = &(cp -> next);
+ }
+
+ if (!cp1) {
+ if (debug > 1) printf(" .. inserting new history entry.\n");
+
+ // Not found on this chain, append it!
+ cp = historydb_alloc(db, pb->packet_len);
+ cp->next = NULL;
+ memcpy(cp->key, keybuf, keylen);
+ cp->key[keylen] = 0; /* zero terminate */
+ cp->keylen = keylen;
+ cp->hash1 = h1;
+
+ cp->lat = pb->lat;
+ cp->coslat = pb->cos_lat;
+ cp->lon = pb->lng;
+ cp->arrivaltime = pb->t;
+ cp->packettype = pb->packettype;
+ cp->flags = pb->flags;
+ cp->last_heard[pb->source_if_group] = pb->t;
+ if (pb->flags & F_HASPOS)
+ cp->positiontime = pb->t;
+
+ cp->packetlen = pb->packet_len;
+ cp->packet = cp->packetbuf; // default case
+ if (cp->packetlen > sizeof(cp->packetbuf)) {
+ // Needs bigger buffer than pre-allocated one,
+ // thus it retrieves that one from heap.
+ cp->packet = malloc( cp->packetlen );
+ }
+
+ *hp = cp;
+ }
+ else
+ return cp1; // != NULL
+
+ return *hp;
+}
+
+
+/* lookup... */
+
+history_cell_t *historydb_lookup(historydb_t *db, const char *keybuf, const int keylen)
+{
+ int i;
+ unsigned int h1;
+ struct history_cell_t *cp;
+
+ // validity is 5 minutes shorter than expiration time..
+ time_t validitytime = tick.tv_sec - lastposition_storetime + 5*60;
+
+ ++db->historydb_lookups;
+
+ h1 = keyhash(keybuf, keylen, 0);
+ i = foldhash(h1);
+
+ cp = db->hash[i];
+
+ if (debug > 1) printf("historydb_lookup(%*s) -> i=%d", keylen, keybuf, i);
+
+ for ( ; cp != NULL ; cp = cp->next ) {
+ if ( (cp->hash1 == h1) &&
+ // Hash match, compare the key
+ (cp->keylen == keylen) ) {
+ if (debug > 1) printf(" .. hash match");
+ if (memcmp(cp->key, keybuf, keylen) == 0) {
+ if (debug > 1) printf(" .. key match");
+ // Key match!
+ if (timecmp(cp->arrivaltime, validitytime) > 0) {
+ if (debug > 1) printf(" .. and not too old\n");
+ return cp;
+ }
+ }
+ }
+ }
+ if (debug > 1) printf(" .. no match\n");
+ return NULL;
+}
+
+
+
+/*
+ * The historydb_cleanup() exists to purge too old data out of
+ * the database at regular intervals. Call this about once a minute.
+ */
+
+static void historydb_cleanup(historydb_t *db)
+{
+ struct history_cell_t **hp, *cp;
+ int i, cleancount = 0;
+
+ if (debug > 1) printf("historydb_cleanup() ");
+
+ time_t expirytime = tick.tv_sec - lastposition_storetime;
+
+ for (i = 0; i < HISTORYDB_HASH_MODULO; ++i) {
+ hp = &db->hash[i];
+
+ // multiple locks ? one for each bucket, or for a subset of buckets ?
+
+ while (( cp = *hp )) {
+ if (timecmp(cp->arrivaltime, expirytime) < 0) {
+ // OLD...
+ *hp = cp->next;
+ cp->next = NULL;
+ historydb_free(cp);
+ ++cleancount;
+ if (debug > 1) printf(" drop(%p) i=%d", cp, i);
+
+ } else {
+ /* No expiry, just advance the pointer */
+ hp = &(cp -> next);
+ }
+ }
+ }
+ if (debug > 1) printf(" .. done.\n");
+}
+
+
+static time_t next_cleanup_time;
+
+int historydb_prepoll(struct aprxpolls *app)
+{
+ return 0;
+}
+
+int historydb_postpoll(struct aprxpolls *app)
+{
+ int i;
+ // Limit next cleanup to be at most 60 second in future
+ // (just in case the system time jumped back)
+ if (next_cleanup_time >= tick.tv_sec+61) {
+ next_cleanup_time = tick.tv_sec + 60;
+ }
+ if (next_cleanup_time >= tick.tv_sec) return 0;
+ next_cleanup_time = tick.tv_sec + 60; // A minute from now..
+
+ for (i = 0; i < _dbs_count; ++i) {
+ historydb_cleanup(_dbs[i]);
+ }
+
+ return 0;
+}
+
+#endif
diff --git a/historydb.h b/historydb.h
new file mode 100644
index 0000000..e52d637
--- /dev/null
+++ b/historydb.h
@@ -0,0 +1,101 @@
+/********************************************************************
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ ********************************************************************/
+
+
+/*
+ * The historydb contains positional packet data in form of:
+ * - position packet
+ * - objects
+ * - items
+ * Keying varies, origination callsign of positions, name
+ * for object/item.
+ *
+ * Inserting does incidential cleanup scanning while traversing
+ * hash chains.
+ *
+ * In APRS-IS there are about 25 000 distinct callsigns or
+ * item or object names with position information PER WEEK.
+ * DB lifetime of 48 hours cuts that down a bit more.
+ * Memory usage is around 3-4 MB
+ *
+ * --------------
+ *
+ * On Tx-IGate the number of distinct callsigns is definitely
+ * lower...
+ *
+ */
+
+#ifndef __HISTORYDB_H__
+#define __HISTORYDB_H__
+
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#define HISTORYDB_HASH_MODULO 128 /* fold bits: 7 & 14 */
+
+struct pbuf_t; // forward declarator
+struct historydb_t; // forward..
+
+typedef struct history_cell_t {
+ struct history_cell_t *next;
+ struct historydb_t *db;
+
+ time_t arrivaltime;
+ time_t positiontime; // When last position was received
+ time_t *last_heard; // Usually points to last_heard_buf[]
+ time_t last_heard_buf[MAX_IF_GROUP];
+
+ float tokenbucket; // Source callsign specific TokenBucket filter
+ // Digi allocates HistoryDb per transmitter.
+
+ uint16_t packettype;
+ uint16_t flags;
+ uint16_t packetlen;
+ uint8_t keylen;
+ char key[CALLSIGNLEN_MAX+2];
+
+ float lat, coslat, lon;
+ uint32_t hash1;
+
+ char *packet;
+ char packetbuf[170]; /* Maybe a dozen packets are bigger than
+ 170 bytes long out of some 17 000 .. */
+} history_cell_t;
+
+typedef struct historydb_t {
+ struct history_cell_t *hash[HISTORYDB_HASH_MODULO];
+
+ // monitor counters and gauges
+ long historydb_inserts;
+ long historydb_lookups;
+ long historydb_hashmatches;
+ long historydb_keymatches;
+ long historydb_cellgauge;
+ long historydb_noposcount;
+} historydb_t;
+
+
+extern void historydb_init(void);
+
+extern historydb_t *historydb_new(void);
+
+extern void historydb_dump(const historydb_t *, FILE *fp);
+
+extern void historydb_atend(void);
+
+extern int historydb_prepoll(struct aprxpolls *app);
+extern int historydb_postpoll(struct aprxpolls *app);
+
+/* insert and lookup... */
+extern history_cell_t *historydb_insert(historydb_t *db, const struct pbuf_t*);
+extern history_cell_t *historydb_insert_(historydb_t *, const struct pbuf_t *, const int);
+extern history_cell_t *historydb_insert_heard(historydb_t *db, const struct pbuf_t*);
+extern history_cell_t *historydb_lookup(historydb_t *db, const char *keybuf, const int keylen);
+
+#endif
diff --git a/hlog.c b/hlog.c
new file mode 100644
index 0000000..1966bb9
--- /dev/null
+++ b/hlog.c
@@ -0,0 +1,561 @@
+/*
+ * aprsc
+ *
+ * (c) Heikki Hannikainen, OH7LZB <hessu at hes.iki.fi>
+ *
+ * This program is licensed under the BSD license, which can be found
+ * in the file LICENSE.
+ *
+ */
+
+/*
+ * log.c
+ *
+ * logging facility with configurable log levels and
+ * logging destinations
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/time.h>
+#include <string.h>
+#include <pthread.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "hlog.h"
+#include "hmalloc.h"
+#include "rwlock.h"
+
+int log_dest = L_DEFDEST; /* Logging destination */
+int log_level = LOG_INFO; /* Logging level */
+int log_facility = LOG_DAEMON; /* Logging facility */
+char *log_name = NULL; /* Logging name */
+
+char log_basename[] = "aprsc.log";
+char *log_dir = NULL; /* Access log directory */
+char *log_fname = NULL; /* Access log file name */
+int log_file = -1; /* If logging to a file, the file name */
+rwlock_t log_file_lock = RWL_INITIALIZER;
+
+char accesslog_basename[] = "aprsc.access.log";
+char *accesslog_dir = NULL; /* Access log directory */
+char *accesslog_fname = NULL; /* Access log file name */
+int accesslog_file = -1; /* Access log fd */
+rwlock_t accesslog_lock = RWL_INITIALIZER;
+
+int log_rotate_size = 0; /* Rotate log when it reaches a given size */
+int log_rotate_num = 5; /* How many logs to keep around */
+
+char *log_levelnames[] = {
+ "EMERG",
+ "ALERT",
+ "CRIT",
+ "ERROR",
+ "WARNING",
+ "NOTICE",
+ "INFO",
+ "DEBUG",
+ NULL
+};
+
+char *log_destnames[] = {
+ "none",
+ "stderr",
+ "syslog",
+ "file",
+ NULL
+};
+
+/*
+ * Quote a string, C-style. dst will be null-terminated, always.
+ */
+
+static int str_quote(char *dst, int dst_len, const char *src, int src_len)
+{
+ int si;
+ int di = 0;
+ int dst_use_len = dst_len - 2; /* leave space for terminating NUL and escaping an escape */
+ unsigned char c;
+
+ for (si = 0; si < src_len; si++) {
+ if (di >= dst_use_len)
+ break;
+
+ c = (unsigned char) src[si];
+
+ /* printable ASCII */
+ if (c >= 0x20 && c < 0x7f) {
+ /* escape the escape (space reserved already) */
+ if (c == '\\')
+ dst[di++] = '\\';
+
+ dst[di++] = c;
+ continue;
+ }
+
+ /* hex escape, is going to take more space */
+ if (di >= dst_use_len - 4)
+ break;
+
+ dst[di++] = '\\';
+ dst[di++] = 'x';
+ di += snprintf(dst + di, 3, "%.2X", c);
+ }
+
+ dst[di++] = 0;
+
+ return di;
+}
+
+/*
+ * Append a formatted string to a dynamically allocated string
+ */
+
+char *str_append(char *s, const char *fmt, ...)
+{
+ va_list args;
+ char buf[LOG_LEN];
+ int len;
+ char *ret;
+
+ va_start(args, fmt);
+ vsnprintf(buf, LOG_LEN, fmt, args);
+ va_end(args);
+ buf[LOG_LEN-1] = 0;
+
+ len = strlen(s);
+ ret = hrealloc(s, len + strlen(buf) + 1);
+ strcpy(ret + len, buf);
+
+ return ret;
+}
+
+/*
+ * Pick a log level
+ */
+
+int pick_loglevel(char *s, char **names)
+{
+ int i;
+
+ for (i = 0; (names[i]); i++)
+ if (!strcasecmp(s, names[i]))
+ return i;
+
+ return -1;
+}
+
+/*
+ * Open log
+ */
+
+int open_log(char *name, int reopen)
+{
+ if (!reopen)
+ rwl_wrlock(&log_file_lock);
+
+ if (log_name)
+ hfree(log_name);
+
+ if (!(log_name = hstrdup(name))) {
+ fprintf(stderr, "aprsc logger: out of memory!\n");
+ exit(1);
+ }
+
+ if (log_dest == L_SYSLOG)
+ openlog(name, LOG_NDELAY|LOG_PID, log_facility);
+
+ if (log_dest == L_FILE) {
+ if (log_fname)
+ hfree(log_fname);
+
+ log_fname = hmalloc(strlen(log_dir) + strlen(log_basename) + 2);
+ sprintf(log_fname, "%s/%s", log_dir, log_basename);
+
+ log_file = open(log_fname, O_WRONLY|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR|S_IRGRP);
+ if (log_file < 0) {
+ fprintf(stderr, "aprsc logger: Could not open %s: %s\n", log_fname, strerror(errno));
+ exit(1);
+ }
+ }
+
+ rwl_wrunlock(&log_file_lock);
+
+ if (log_dest == L_FILE)
+ hlog(LOG_DEBUG, "Log file %s %sopened on fd %d", log_fname, (reopen) ? "re" : "", log_file);
+
+ return 0;
+}
+
+/*
+ * Close log
+ */
+
+int close_log(int reopen)
+{
+ hlog(LOG_DEBUG, "close_log");
+
+ char *s = NULL;
+ if (log_name)
+ s = hstrdup(log_name);
+
+ rwl_wrlock(&log_file_lock);
+
+ if (log_name) {
+ hfree(log_name);
+ log_name = NULL;
+ }
+
+ if (log_dest == L_SYSLOG) {
+ closelog();
+ } else if (log_dest == L_FILE) {
+ if (log_file >= 0) {
+ if (close(log_file))
+ fprintf(stderr, "aprsc logger: Could not close log file %s: %s\n", log_fname, strerror(errno));
+ log_file = -1;
+ }
+ if (log_fname) {
+ hfree(log_fname);
+ log_fname = NULL;
+ }
+ }
+
+ if (reopen && s)
+ open_log(s, 1);
+
+ if (!reopen)
+ rwl_wrunlock(&log_file_lock);
+
+ if (s)
+ hfree(s);
+
+ return 0;
+}
+
+/*
+ * Rotate the log file
+ */
+
+int rotate_log(void)
+{
+ char *tmp;
+ int i;
+ char *r1, *r2;
+
+ if (rwl_trywrlock(&log_file_lock)) {
+ fprintf(stderr, "failed to wrlock log_file_lock for rotation\n");
+ return 0;
+ }
+
+ // check if still oversize and not rotated by another thread
+ off_t l = lseek(log_file, 0, SEEK_CUR);
+ if (l < log_rotate_size) {
+ rwl_wrunlock(&log_file_lock);
+ return 0;
+ }
+
+ // rename
+ tmp = hmalloc(strlen(log_fname) + 6);
+ sprintf(tmp, "%s.tmp", log_fname);
+ if (rename(log_fname, tmp) != 0) {
+ fprintf(stderr, "aprsc logger: Failed to rename %s to %s: %s\n", log_fname, tmp, strerror(errno));
+ // continue anyway, try to reopen
+ }
+
+ // reopen
+ if (close(log_file))
+ fprintf(stderr, "aprsc logger: Could not close log file %s: %s\n", log_fname, strerror(errno));
+
+ log_file = open(log_fname, O_WRONLY|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR|S_IRGRP);
+ if (log_file < 0) {
+ fprintf(stderr, "aprsc logger: Could not open %s: %s\n", log_fname, strerror(errno));
+ log_file = -1;
+ }
+
+ rwl_wrunlock(&log_file_lock);
+
+ // do the rest of the rotation
+ r1 = hmalloc(strlen(log_fname) + 16);
+ r2 = hmalloc(strlen(log_fname) + 16);
+
+ for (i = log_rotate_num-1; i > 0; i--) {
+ sprintf(r1, "%s.%d", log_fname, i-1);
+ sprintf(r2, "%s.%d", log_fname, i);
+ if (rename(r1, r2) != 0 && errno != ENOENT) {
+ fprintf(stderr, "rename %s => %s failed:%s\n", r1, r2, strerror(errno));
+ }
+ }
+
+ if (rename(tmp, r1) != 0) {
+ fprintf(stderr, "aprsc logger: Failed to rename %s to %s: %s\n", tmp, r1, strerror(errno));
+ }
+
+ hfree(tmp);
+ hfree(r1);
+ hfree(r2);
+
+ return 0;
+}
+
+static int hlog_write(int priority, const char *s)
+{
+ struct tm lt;
+ struct timeval tv;
+ char wb[LOG_LEN];
+ int len, w;
+
+ // Wall clock time
+ gettimeofday(&tv, NULL);
+ gmtime_r(&tv.tv_sec, <);
+
+ if (log_dest & L_STDERR) {
+ rwl_rdlock(&log_file_lock);
+ fprintf(stderr, "%4d/%02d/%02d %02d:%02d:%02d.%06d %s[%d:%lx] %s: %s\n",
+ lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday, lt.tm_hour, lt.tm_min, lt.tm_sec, (int)tv.tv_usec,
+ (log_name) ? log_name : "aprsc", (int)getpid(), (unsigned long int)pthread_self(), log_levelnames[priority], s);
+ rwl_rdunlock(&log_file_lock);
+
+ }
+
+ if ((log_dest & L_FILE) && (log_file >= 0)) {
+ len = snprintf(wb, LOG_LEN, "%4d/%02d/%02d %02d:%02d:%02d.%06d %s[%d:%lx] %s: %s\n",
+ lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday, lt.tm_hour, lt.tm_min, lt.tm_sec, (int)tv.tv_usec,
+ (log_name) ? log_name : "aprsc", (int)getpid(), (unsigned long int)pthread_self(), log_levelnames[priority], s);
+ wb[LOG_LEN-1] = 0;
+ rwl_rdlock(&log_file_lock);
+ if ((w = write(log_file, wb, len)) != len)
+ fprintf(stderr, "aprsc logger: Could not write to %s (fd %d): %s\n", log_fname, log_file, strerror(errno));
+ rwl_rdunlock(&log_file_lock);
+
+ if (log_rotate_size) {
+ off_t l = lseek(log_file, 0, SEEK_CUR);
+ if (l >= log_rotate_size) {
+ rotate_log();
+ }
+ }
+
+ }
+
+ if (log_dest & L_SYSLOG) {
+ rwl_rdlock(&log_file_lock);
+ syslog(priority, "%s: %s", log_levelnames[priority], s);
+ rwl_rdunlock(&log_file_lock);
+ }
+
+ return 1;
+}
+
+/*
+ * Log a message with a packet (will be quoted)
+ */
+
+int hlog(int priority, const char *fmt, ...)
+{
+ va_list args;
+ char s[LOG_LEN];
+
+ if (priority > 7)
+ priority = 7;
+ else if (priority < 0)
+ priority = 0;
+
+ if (priority > log_level)
+ return 0;
+
+ va_start(args, fmt);
+ vsnprintf(s, LOG_LEN, fmt, args);
+ va_end(args);
+
+ return hlog_write(priority, s);
+}
+
+
+/*
+ * Log a message, with a packet in the end.
+ * Packet will be quoted.
+ */
+
+int hlog_packet(int priority, const char *packet, int packetlen, const char *fmt, ...)
+{
+ va_list args;
+ char s[LOG_LEN];
+ int l;
+
+ if (priority > 7)
+ priority = 7;
+ else if (priority < 0)
+ priority = 0;
+
+ if (priority > log_level)
+ return 0;
+
+ va_start(args, fmt);
+ l = vsnprintf(s, LOG_LEN, fmt, args);
+ va_end(args);
+
+ str_quote(s + l, LOG_LEN - l, packet, packetlen);
+
+ return hlog_write(priority, s);
+}
+
+/*
+ * Open access log
+ */
+
+int accesslog_open(char *logd, int reopen)
+{
+ if (!reopen)
+ rwl_wrlock(&accesslog_lock);
+
+ if (accesslog_fname)
+ hfree(accesslog_fname);
+
+ if (accesslog_dir)
+ hfree(accesslog_dir);
+
+ accesslog_dir = hstrdup(logd);
+ accesslog_fname = hmalloc(strlen(accesslog_dir) + strlen(accesslog_basename) + 2);
+ sprintf(accesslog_fname, "%s/%s", accesslog_dir, accesslog_basename);
+
+ accesslog_file = open(accesslog_fname, O_WRONLY|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR|S_IRGRP);
+ if (accesslog_file < 0)
+ hlog(LOG_CRIT, "Could not open %s: %s", accesslog_fname, strerror(errno));
+
+ rwl_wrunlock(&accesslog_lock);
+
+ return accesslog_file;
+}
+
+/*
+ * Close access log
+ */
+
+int accesslog_close(char *reopenpath)
+{
+ hlog(LOG_DEBUG, "Closing access log...");
+ rwl_wrlock(&accesslog_lock);
+ hlog(LOG_DEBUG, "Closing access log, got lock");
+
+ if (close(accesslog_file))
+ hlog(LOG_CRIT, "Could not close %s: %s", accesslog_fname, strerror(errno));
+ hfree(accesslog_fname);
+ hfree(accesslog_dir);
+ accesslog_fname = accesslog_dir = NULL;
+ accesslog_file = -1;
+
+ if (reopenpath) {
+ return accesslog_open(reopenpath, 1);
+ } else {
+ rwl_wrunlock(&accesslog_lock);
+ return 0;
+ }
+}
+
+/*
+ * Log an access log message
+ */
+
+int accesslog(const char *fmt, ...)
+{
+ va_list args;
+ char s[LOG_LEN], wb[LOG_LEN];
+ time_t t;
+ struct tm lt;
+ int len;
+ ssize_t w;
+
+ va_start(args, fmt);
+ vsnprintf(s, LOG_LEN, fmt, args);
+ va_end(args);
+ s[LOG_LEN-1] = 0;
+
+ time(&t);
+ gmtime_r(&t, <);
+
+ len = snprintf(wb, LOG_LEN, "[%4.4d/%2.2d/%2.2d %2.2d:%2.2d:%2.2d] %s\n",
+ lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday, lt.tm_hour, lt.tm_min, lt.tm_sec, s);
+ wb[LOG_LEN-1] = 0;
+
+ rwl_rdlock(&accesslog_lock);
+ if (accesslog_file >= 0) {
+ if ((w = write(accesslog_file, wb, len)) != len)
+ hlog(LOG_CRIT, "Could not write to %s (fd %d): %s", accesslog_fname, accesslog_file, strerror(errno));
+ } else {
+ if (accesslog_file != -666) {
+ hlog(LOG_ERR, "Access log not open, log lines are lost!");
+ accesslog_file = -666;
+ }
+ }
+ rwl_rdunlock(&accesslog_lock);
+
+ return 1;
+}
+
+/*
+ * Write my PID to file, after locking the pid file.
+ * Leaves the file descriptor open so that the lock will be held
+ * as long as the process is running.
+ */
+
+int pidfile_fd = -1;
+
+int writepid(char *name)
+{
+ int f;
+ char s[32];
+ int l;
+
+ f = open(name, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
+ if (f < 0) {
+ hlog(LOG_CRIT, "Could not open %s for writing: %s",
+ name, strerror(errno));
+ return 0;
+ }
+
+ pidfile_fd = f;
+
+ if (flock(f, LOCK_EX|LOCK_NB) < 0) {
+ if (errno == EWOULDBLOCK) {
+ hlog(LOG_CRIT, "Could not lock pid file file %s, another process has a lock on it. Another process running - bailing out.", name);
+ } else {
+ hlog(LOG_CRIT, "Failed to lock pid file %s: %s", name, strerror(errno));
+ }
+ return 0;
+ }
+
+ l = snprintf(s, 32, "%ld\n", (long)getpid());
+
+ if (ftruncate(f, 0) < 0) {
+ hlog(LOG_CRIT, "Could not truncate pid file %s: %s",
+ name, strerror(errno));
+ return 0;
+ }
+
+ if (write(f, s, l) != l) {
+ hlog(LOG_CRIT, "Could not write pid to %s: %s",
+ name, strerror(errno));
+ return 0;
+ }
+
+ return 1;
+}
+
+int closepid(void)
+{
+ if (pidfile_fd >= 0) {
+ if (close(pidfile_fd) != 0) {
+ hlog(LOG_CRIT, "Could not close pid file: %s", strerror(errno));
+ return -1;
+ }
+ pidfile_fd = -1;
+ }
+
+ return 0;
+}
diff --git a/hlog.h b/hlog.h
new file mode 100644
index 0000000..486bee2
--- /dev/null
+++ b/hlog.h
@@ -0,0 +1,57 @@
+/*
+ * aprsc
+ *
+ * (c) Heikki Hannikainen, OH7LZB <hessu at hes.iki.fi>
+ *
+ * This program is licensed under the BSD license, which can be found
+ * in the file LICENSE.
+ *
+ */
+
+#ifndef LOG_H
+#define LOG_H
+
+#define LOG_LEN 2048
+
+#define L_STDERR 1 /* Log to stderror */
+#define L_SYSLOG (1 << 1) /* Log to syslog */
+#define L_FILE (1 << 2) /* Log to a file */
+
+#ifdef __CYGWIN__
+#define L_DEFDEST L_FILE
+#else
+#define L_DEFDEST L_STDERR
+#endif
+
+#define LOG_LEVELS "emerg alert crit err warning notice info debug"
+#define LOG_DESTS "syslog stderr file"
+
+#include <syslog.h>
+
+extern char *log_levelnames[];
+extern char *log_destnames[];
+
+extern int log_dest; /* Logging destination */
+extern int log_level; /* Logging level */
+extern char *log_dir; /* Log directory */
+
+extern int log_rotate_size; /* Rotate log when it reaches a given size */
+extern int log_rotate_num; /* How many logs to keep around */
+
+
+extern char *str_append(char *s, const char *fmt, ...);
+
+extern int pick_loglevel(char *s, char **names);
+extern int open_log(char *name, int reopen);
+extern int close_log(int reopen);
+extern int hlog(int priority, const char *fmt, ...);
+extern int hlog_packet(int priority, const char *packet, int packetlen, const char *fmt, ...);
+
+extern int accesslog_open(char *logd, int reopen);
+extern int accesslog_close(char *reopenpath);
+extern int accesslog(const char *fmt, ...);
+
+extern int writepid(char *name);
+extern int closepid(void);
+
+#endif
diff --git a/igate.c b/igate.c
new file mode 100644
index 0000000..32c36bd
--- /dev/null
+++ b/igate.c
@@ -0,0 +1,650 @@
+/* **************************************************************** *
+ * *
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ * IGATE: Pass packets in between RF network and APRS-IS *
+ * *
+ * **************************************************************** */
+
+#include "aprx.h"
+
+
+const char *tnc2_verify_callsign_format(const char *t, int starok, int strictax25, const char *e)
+{
+ const char *s = t;
+
+ for (; *s && s < e; ++s) {
+ /* Valid station-id charset is: [A-Z0-9] */
+ int c = *s;
+ if (!(('A' <= c && c <= 'Z') || ('0' <= c && c <= '9'))) {
+ /* Not A-Z, 0-9 */
+ break;
+ }
+ }
+ /* Now *s can be any of: '>,*-:' */
+ if (*s == '-') {
+ /* Minus and digits.. */
+ ++s;
+ if (strictax25) {
+ if ('1' <= *s && *s <= '9')
+ ++s;
+ if ('0' <= *s && *s <= '9')
+ ++s;
+ } else {
+ // Up to 2 of any alphanumeric
+ if (('0' <= *s && *s <= '9') ||
+ ('a' <= *s && *s <= 'z') ||
+ ('A' <= *s && *s <= 'Z'))
+ ++s;
+ if (('0' <= *s && *s <= '9') ||
+ ('a' <= *s && *s <= 'z') ||
+ ('A' <= *s && *s <= 'Z'))
+ ++s;
+ }
+ }
+
+ if (*s == '*' /* && starok */ ) /* Star is present at way too many
+ SRC and DEST addresses, it is not
+ limited to VIA fields :-( */
+ ++s;
+
+ if (s > e) {
+ if (debug)
+ printf("callsign scanner ran over end of buffer");
+ return NULL; /* Over the end-of-buffer */
+ }
+ if (s == t) {
+ if (debug)
+ printf("%s callsign format verify got bad character: '%c' in string: '%.20s'\n", strictax25 ? "Strict":"Lenient", *s, t);
+ return NULL; /* Too short ? */
+ }
+
+ if (*s != '>' && *s != ',' && *s != ':' && *s != 0) {
+ /* Terminates badly.. */
+ if (debug)
+ printf("%s callsign format verify got bad character: '%c' in string: '%.20s'\n", strictax25 ? "Strict":"Lenient", *s, t);
+ return NULL;
+ }
+
+ return s;
+}
+
+#ifndef DISABLE_IGATE
+
+
+/*
+ * igate start -- make TX-igate allocations and inits
+ */
+void igate_start()
+{
+ // Always relay all traffic from RF to APRSIS, other
+ // direction is handled per transmitter interface...
+ // enable_aprsis_rx_dupecheck();
+}
+
+static const char *tnc2_forbidden_source_stationid(const char *t, const int strictax25,const char *e)
+{
+ const char *s;
+
+ s = tnc2_verify_callsign_format(t, 0, strictax25, e);
+ if (!s)
+ return NULL;
+
+ if (memcmp("WIDE", t, 4) == 0 || /* just plain wrong setting */
+ memcmp("RELAY", t, 5) == 0 || /* just plain wrong setting */
+ memcmp("TRACE", t, 5) == 0 || /* just plain wrong setting */
+ memcmp("TCPIP", t, 5) == 0 || /* just plain wrong setting */
+ memcmp("TCPXX", t, 5) == 0 || /* just plain wrong setting */
+ memcmp("N0CALL", t, 6) == 0 || /* TNC default setting */
+ memcmp("NOCALL", t, 6) == 0) /* TNC default setting */
+ return NULL;
+
+ return s;
+}
+
+static const char *tnc2_forbidden_destination_stationid(const char *t, const int strictax25, const char *e)
+{
+ const char *s;
+
+ s = tnc2_verify_callsign_format(t, 0, strictax25, e);
+ if (!s)
+ return NULL;
+
+ if (memcmp("TCPIP", t, 5) == 0 || /* just plain wrong */
+ memcmp("TCPXX", t, 5) == 0 || /* Forbidden to gate */
+ memcmp("NOGATE", t, 5) == 0 || /* Forbidden to gate */
+ memcmp("RFONLY", t, 5) == 0 || /* Forbidden to gate */
+ memcmp("N0CALL", t, 6) == 0 || /* TNC default setting */
+ memcmp("NOCALL", t, 6) == 0) /* TNC default setting */
+ return NULL;
+
+ return s;
+}
+
+static const char *tnc2_forbidden_via_stationid(const char *t, const int strictax25, const char *e)
+{
+ const char *s;
+
+ s = tnc2_verify_callsign_format(t, 1, strictax25, e);
+ if (!s)
+ return NULL;
+
+ if (memcmp("RFONLY", t, 6) == 0 ||
+ memcmp("NOGATE", t, 6) == 0 ||
+ memcmp("TCPIP", t, 5) == 0 ||
+ memcmp("TCPXX", t, 5) == 0)
+ return NULL;
+
+ return s;
+}
+
+/*
+static int tnc2_forbidden_data(const char *t)
+{
+ int i;
+
+ for (i = 0; i < dataregscount; ++i) {
+ int stat = regexec(dataregs[i], t, 0, NULL, 0);
+ if (stat == 0)
+ return 1; // MATCH!
+ }
+
+ return 0;
+}
+*/
+
+void verblog(const char *portname, int istx, const char *tnc2buf, int tnc2len) {
+ if (verbout) {
+ printf("%ld\t%-9s ", (long) tick.tv_sec, portname);
+ printf("%s \t", istx ? "T":"R");
+ fwrite(tnc2buf, tnc2len, 1, stdout);
+ printf("\n");
+ }
+}
+
+/*
+ * The tnc2_rxgate() is actual RX-iGate filter function, and processes
+ * prepated TNC2 format text presentation of the packet.
+ * It does presume that the record is in a buffer that can be written on!
+ */
+
+void igate_to_aprsis(const char *portname, const int tncid, const char *tnc2buf, int tnc2addrlen, int tnc2len, const int discard0, const int strictax25_)
+{
+ const char *tp, *t, *t0;
+ const char *s;
+ const char *ae;
+ const char *e;
+ int discard = discard0;
+ int strictax25 = strictax25_; // Callsigns per strict AX25 (not 3rd-party)
+
+ tp = tnc2buf; // 3rd-party recursion moves tp
+ ae = tp + tnc2addrlen; // 3rd-party recursion moves ae
+ e = tp + tnc2len; // stays the same all the time
+
+ rflog(portname, 'R', discard, tp, tnc2len);
+
+ redo_frame_filter:;
+
+ t = tp;
+ t0 = NULL;
+
+ /* t == beginning of the TNC2 format packet */
+
+ /*
+ * If any of following matches, discard the packet!
+ * next if ($axpath =~ m/^WIDE/io); # Begins with = is sourced by..
+ * next if ($axpath =~ m/^RELAY/io);
+ * next if ($axpath =~ m/^TRACE/io);
+ */
+ s = tnc2_forbidden_source_stationid(t, strictax25, e);
+ if (s)
+ t = (char *) s;
+ else {
+ /* Forbidden in source fields.. */
+ if (debug)
+ printf("TNC2 forbidden source stationid: '%.20s'\n", t);
+ goto discard;
+ }
+
+ /* SOURCE>DESTIN,VIA,VIA:payload */
+
+ if (*t == '>') {
+ ++t;
+ } else {
+ if (debug)
+ printf("TNC2 bad address format, expected '>', got: '%.20s'\n", t);
+ goto discard;
+ }
+
+ s = tnc2_forbidden_destination_stationid(t, strictax25, e);
+ if (s)
+ t = (char *) s;
+ else {
+ if (debug)
+ printf("TNC2 forbidden (by REGEX) destination stationid: '%.20s'\n", t);
+ goto discard;
+ }
+
+ while (*t && t < ae) {
+ if (*t == ',') {
+ ++t;
+ } else {
+ if (debug)
+ printf("TNC2 via address syntax bug, wanted ',' or ':', got: '%.20s'\n", t);
+ goto discard;
+ }
+
+ /*
+ * next if ($axpath =~ m/RFONLY/io); # Has any of these in via fields..
+ * next if ($axpath =~ m/TCPIP/io);
+ * next if ($axpath =~ m/TCPXX/io);
+ * next if ($axpath =~ m/NOGATE/io); # .. drop it.
+ */
+
+ s = tnc2_forbidden_via_stationid(t, strictax25, e);
+ if (!s) {
+ /* Forbidden in via fields.. */
+ if (debug)
+ printf("TNC2 forbidden VIA stationid, got: '%.20s'\n", t);
+ goto discard;
+ } else
+ t = (char *) s;
+
+
+ }
+ /* Now we have processed the address, this should be ABORT time if
+ the current character is not ':' ! */
+ if (*t == ':') {
+#if 0
+ // *t++ = 0; /* turn it to NUL character */
+#else
+ /* Don't zero! */
+ ++t;
+#endif
+ ;
+ } else {
+ if (debug)
+ printf("TNC2 address parsing did not find ':': '%.20s'\n",t);
+ goto discard;
+ }
+ t0 = t; // Start of payload
+
+ /* Now 't' points to data.. */
+
+
+/*
+ if (tnc2_forbidden_data(t)) {
+ if (debug)
+ printf("Forbidden data in TNC2 packet - REGEX match");
+ goto discard;
+ }
+*/
+
+ /* Will not relay messages that begin with '?' char: */
+ if (*t == '?') {
+ if (debug)
+ printf("Will not relay packets where payload begins with '?'\n");
+ goto discard;
+ }
+
+ /* Messages begining with '}' char are 3rd-party frames.. */
+ if (*t == '}') {
+ /* DEBUG OUTPUT TO STDOUT ! */
+ verblog(portname, 0, tp, tnc2len);
+
+ strictax25 = 0;
+ /* Copy the 3rd-party message content into begining of the buffer... */
+ ++t; /* Skip the '}' */
+ tp = t;
+ tnc2len = e - t; /* New length */
+ // end pointer (e) does not change
+ // Address end must be searched again
+ ae = memchr(tp, ':', tnc2len);
+ if (ae == NULL) {
+ // Bad 3rd-party frame
+ goto discard;
+ }
+ tnc2addrlen = (int)(ae - tp);
+
+ /* .. and redo the filtering. */
+ goto redo_frame_filter;
+ }
+
+
+
+ /* TODO: Verify message being of recognized APRS packet type */
+ /* '\0x60', '\0x27': MIC-E, len >= 9
+ * '!','=','/','{': Normal or compressed location packet..
+ * '$': NMEA data, if it begins as '$GP'
+ * '$': WX data (maybe) if not NMEA data
+ * ';': Object data, len >= 31
+ * ')': Item data, len >= 18
+ * ':': message, bulletin or aanouncement, len >= 11
+ * '<': Station Capabilities, len >= 2
+ * '>': Status report
+ * '}': Third-party message
+ * ... and many more ...
+ */
+
+ // FIXME: Duplicate filter messages to APRSIS
+
+
+ /* _NO_ ending CRLF, the APRSIS subsystem adds it. */
+
+ /*
+ printf("alen=%d tlen=%d tnc2buf=%s\n",t0-1-tnc2buf, e-t0, tnc2buf);
+ */
+ discard = aprsis_queue(tp, tnc2addrlen, qTYPE_IGATED, portname, t0, e - t0); /* Send it.. */
+ /* DEBUG OUTPUT TO STDOUT ! */
+ verblog(portname, 0, tp, tnc2len);
+
+ // Log the innermost form of packet to be sent out..
+ if (tp != tnc2buf || discard != discard0)
+ rflog(portname, 'R', discard, tp, tnc2len);
+
+ if (0) {
+ discard:;
+
+ discard = -1;
+ }
+
+ if (discard) {
+ erlang_add(portname, ERLANG_DROP, tnc2len, 1);
+ }
+}
+
+
+
+/* ---------------------------------------------------------- */
+
+
+/*
+ * Study APRS-IS received message's address header part
+ * to determine if it is not to be relayed back to RF..
+ */
+static int forbidden_to_gate_addr(const char *s)
+{
+ if (memcmp(s, "TCPXX", 5) == 0)
+ return 1; /* Forbidden to be relayed */
+ if (memcmp(s, "NOGATE", 6) == 0)
+ return 1; /* Forbidden to be relayed */
+ if (memcmp(s, "RFONLY", 6) == 0)
+ return 1; /* Forbidden to be relayed */
+ if (memcmp(s, "qAX", 3) == 0)
+ return 1;
+
+ return 0; /* Found nothing forbidden */
+}
+
+
+/*
+ * For APRSIS -> APRX -> RF gatewaying.
+ * Have to convert incoming TNC2 format messge to AX.25..
+ *
+ * See: http://www.aprs-is.net/IGateDetails.aspx
+ *
+ * ----------------------------------------------------------------
+ *
+ * Gate message packets and associated posits to RF if
+ *
+ * 1. the receiving station has been heard within range within
+ * a predefined time period (range defined as digi hops,
+ * distance, or both).
+ * 2. the sending station has not been heard via RF within
+ * a predefined time period (packets gated from the Internet
+ * by other stations are excluded from this test).
+ * 3. the sending station does not have TCPXX, NOGATE, or RFONLY
+ * in the header.
+ * 4. the receiving station has not been heard via the Internet
+ * within a predefined time period.
+ *
+ * A station is said to be heard via the Internet if packets from
+ * the station contain TCPIP* or TCPXX* in the header or if gated
+ * (3rd-party) packets are seen on RF gated by the station and
+ * containing TCPIP or TCPXX in the 3rd-party header (in other
+ * words, the station is seen on RF as being an IGate).
+ *
+ * Gate all packets to RF based on criteria set by the sysop (such
+ * as callsign, object name, etc.).
+ *
+ * ----------------------------------------------------------------
+ *
+ * TODO:
+ * a) APRS-IS relayed third-party frames are ignored.
+ *
+ * 3) The message path does not have TCPXX, NOGATE, RFONLY
+ * in it.
+ *
+ * Following steps are done in interface_receive_3rdparty()
+ *
+ * 1) The receiving station has been heard recently
+ * within defined range limits, and more recently
+ * than since given interval T1. (Range as digi-hops [N1]
+ * or coordinates, or both.)
+ *
+ * 2) The sending station has not been heard via RF
+ * within timer interval T2. (Third-party relayed
+ * frames are not analyzed for this.)
+ *
+ * 4) the receiving station has not been heard via the Internet
+ * within a predefined time period.
+ * A station is said to be heard via the Internet if packets
+ * from the station contain TCPIP* or TCPXX* in the header or
+ * if gated (3rd-party) packets are seen on RF gated by the
+ * station and containing TCPIP or TCPXX in the 3rd-party
+ * header (in other words, the station is seen on RF as being
+ * an IGate).
+ *
+ * 5) Gate all packets to RF based on criteria set by the sysop
+ * (such as callsign, object name, etc.).
+ *
+ * c) Drop everything else.
+ *
+ * Paths
+ *
+ * IGates should use the 3rd-party format on RF of
+ * IGATECALL>APRS,GATEPATH}FROMCALL>TOCALL,TCPIP,IGATECALL*:original packet data
+ * where GATEPATH is the path that the gated packet is to follow
+ * on RF. This format will allow IGates to prevent gating the packet
+ * back to APRS-IS.
+ *
+ * q constructs should never appear on RF.
+ * The I construct should never appear on RF.
+ * Except for within gated packets, TCPIP and TCPXX should not be
+ * used on RF.
+ */
+
+static void pick_heads(char *ax25, int headlen,
+ char *heads[20], int *headscount)
+{
+ char *p = ax25;
+ char *e = ax25 + headlen;
+
+ char *p0 = p;
+ // if (debug)printf(" head parse: ");
+ while (p <= e) {
+ p0 = p;
+ while (p <= e) {
+ const char c = *p;
+ if (c != '>' && c != ',' && c != ':') {
+ ++p;
+ continue;
+ }
+ *p++ = 0;
+ if (*headscount >= 19)
+ continue; /* too many head parts.. */
+ heads[*headscount] = p0;
+ *headscount += 1;
+ // if (debug) printf(" %-9s", p0);
+ break;
+ }
+ }
+ heads[*headscount] = NULL;
+ // if (debug)printf("\n");
+}
+
+static void aprsis_commentframe(const char *tnc2buf, int tnc2len) {
+ // TODO .. #TICK -> #TOCK ??
+}
+
+void igate_from_aprsis(const char *ax25, int ax25len)
+{
+ // const char *p = ax25;
+ int colonidx;
+ int ok_to_relay, i;
+ const char *b;
+ // const char *e = p + ax25len; /* string end pointer */
+// char axbuf[3000]; /* enough and then some more.. */
+ // char axbuf2[1000]; /* enough and then some more.. */
+ char *heads[20];
+ char *headsbuf;
+ int headscount = 0;
+// char *s;
+ char *fromcall = NULL;
+ char *origtocall = NULL;
+
+ if (ax25[0] == '#') { // Comment line, timer tick, something such...
+ aprsis_commentframe(ax25, ax25len);
+ return;
+ }
+
+ if (ax25len > 520) {
+ /* Way too large a frame... */
+ if (debug)printf("APRSIS dataframe length is too large! (%d)\n",ax25len);
+ return;
+ }
+
+ b = memchr(ax25, ':', ax25len);
+ if (b == NULL) {
+ if (debug)printf("APRSIS dataframe does not have ':' in it\n");
+ return; // Huh? No double-colon on line, it is not proper packet line
+ }
+
+ colonidx = b-ax25;
+ if (colonidx+3 >= ax25len) {
+ /* Not really any data there.. */
+ if (debug)printf("APRSIS dataframe too short to contain anything\n");
+ return;
+ }
+
+ rflog("APRSIS",'R',0,ax25, ax25len);
+
+ headsbuf = alloca(colonidx+1);
+ memcpy(headsbuf, ax25, colonidx+1);
+
+ headscount = 0;
+ pick_heads(headsbuf, colonidx, heads, &headscount);
+
+ ok_to_relay = 0;
+ if (headscount < 4) {
+ // Less than 3 header fields coming from APRS-IS ?
+ if (debug)
+ printf("Not relayable packet! [1]\n");
+ return;
+ }
+
+ if (memcmp(heads[1],"RXTLM-",6)==0) {
+ if (debug)
+ printf("Not relayable packet! [2]\n");
+ return;
+ }
+
+ fromcall = heads[0];
+ origtocall = heads[1];
+ for (i = 0; i < headscount; ++i) {
+ /* 3) */
+ if (forbidden_to_gate_addr(heads[i])) {
+ if (debug)
+ printf("Not relayable packet! [3]\n");
+ return;
+ }
+
+// FIXME: Hmm.. Really ?? q-construct analysis and "ok_to_relay" decission
+ if (heads[i][0] == 'q' && heads[i][1] == 'A') {
+ int qcode = heads[i][2];
+ ok_to_relay = 1;
+
+ // Depending on qA? value, following may
+ // actually be fromcall, or some other..
+ switch (qcode) {
+ case 'X':
+ ok_to_relay = 0;
+ break;
+ default:
+ break;
+ }
+
+ heads[i] = NULL;
+ break;
+ }
+ }
+ if (!ok_to_relay) {
+ if (debug)
+ printf("Not relayable packet! [4]\n");
+ return;
+ }
+
+ ++b; /* Skip the ':' */
+
+ /* a) */
+ /* Check for forbidden things that cause dropping the packet */
+ if (*b == '}') { /* Third-party packet from APRS-IS */
+ if (debug)
+ printf("Not relayable packet! [5]\n");
+ return; /* drop it */
+ }
+
+ // Following logic steps are done in interface_receive_3rdparty!
+
+ // FIXME: 1) - verify receiving station has been heard recently on radio
+ // FIXME: 2) - sending station has not been heard recently on radio
+ // FIXME: 4) - the receiving station has not been heard via the Internet within a predefined time period.
+ // FIXME: f) - ??
+
+ if (debug) printf(".. igate from aprsis\n");
+
+ interface_receive_3rdparty( &aprsis_interface,
+ fromcall, origtocall, "TCPIP",
+ b, ax25len - (b-ax25) );
+}
+
+#endif
+
+/* ---------------------------------------------------------- */
+
+void rflog(const char *portname, char direction, int discard, const char *tnc2buf, int tnc2len)
+{
+ if (rflogfile) {
+ FILE *fp = NULL;
+ if (strcmp("-",rflogfile)==0) {
+ if (debug < 2) return;
+ fp = stdout;
+ } else {
+ fp = fopen(rflogfile, "a");
+ }
+
+ if (fp) {
+ char timebuf[60];
+ printtime(timebuf, sizeof(timebuf));
+
+ (void)fprintf(fp, "%s %-9s ", timebuf, portname);
+ (void)fprintf(fp, "%c ", direction);
+
+ if (discard < 0) {
+ fprintf(fp, "*");
+ }
+ if (discard > 0) {
+ fprintf(fp, "#");
+ }
+ (void)fwrite( tnc2buf, tnc2len, 1, fp);
+ (void)fprintf( fp, "\n" );
+
+ if (fp != stdout)
+ fclose(fp);
+ }
+ }
+}
diff --git a/install-sh b/install-sh
new file mode 100755
index 0000000..4fbbae7
--- /dev/null
+++ b/install-sh
@@ -0,0 +1,507 @@
+#!/bin/sh
+# install - install a program, script, or datafile
+
+scriptversion=2006-10-14.15
+
+# This originates from X11R5 (mit/util/scripts/install.sh), which was
+# later released in X11R6 (xc/config/util/install.sh) with the
+# following copyright and license.
+#
+# Copyright (C) 1994 X Consortium
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
+# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the name of the X Consortium shall not
+# be used in advertising or otherwise to promote the sale, use or other deal-
+# ings in this Software without prior written authorization from the X Consor-
+# tium.
+#
+#
+# FSF changes to this file are in the public domain.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# `make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch.
+
+nl='
+'
+IFS=" "" $nl"
+
+# set DOITPROG to echo to test this script
+
+# Don't use :- since 4.3BSD and earlier shells don't like it.
+doit="${DOITPROG-}"
+if test -z "$doit"; then
+ doit_exec=exec
+else
+ doit_exec=$doit
+fi
+
+# Put in absolute file names if you don't have them in your path;
+# or use environment vars.
+
+mvprog="${MVPROG-mv}"
+cpprog="${CPPROG-cp}"
+chmodprog="${CHMODPROG-chmod}"
+chownprog="${CHOWNPROG-chown}"
+chgrpprog="${CHGRPPROG-chgrp}"
+stripprog="${STRIPPROG-strip}"
+rmprog="${RMPROG-rm}"
+mkdirprog="${MKDIRPROG-mkdir}"
+
+posix_glob=
+posix_mkdir=
+
+# Desired mode of installed file.
+mode=0755
+
+chmodcmd=$chmodprog
+chowncmd=
+chgrpcmd=
+stripcmd=
+rmcmd="$rmprog -f"
+mvcmd="$mvprog"
+src=
+dst=
+dir_arg=
+dstarg=
+no_target_directory=
+
+usage="Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
+ or: $0 [OPTION]... SRCFILES... DIRECTORY
+ or: $0 [OPTION]... -t DIRECTORY SRCFILES...
+ or: $0 [OPTION]... -d DIRECTORIES...
+
+In the 1st form, copy SRCFILE to DSTFILE.
+In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
+In the 4th, create DIRECTORIES.
+
+Options:
+-c (ignored)
+-d create directories instead of installing files.
+-g GROUP $chgrpprog installed files to GROUP.
+-m MODE $chmodprog installed files to MODE.
+-o USER $chownprog installed files to USER.
+-s $stripprog installed files.
+-t DIRECTORY install into DIRECTORY.
+-T report an error if DSTFILE is a directory.
+--help display this help and exit.
+--version display version info and exit.
+
+Environment variables override the default commands:
+ CHGRPPROG CHMODPROG CHOWNPROG CPPROG MKDIRPROG MVPROG RMPROG STRIPPROG
+"
+
+while test $# -ne 0; do
+ case $1 in
+ -c) shift
+ continue;;
+
+ -d) dir_arg=true
+ shift
+ continue;;
+
+ -g) chgrpcmd="$chgrpprog $2"
+ shift
+ shift
+ continue;;
+
+ --help) echo "$usage"; exit $?;;
+
+ -m) mode=$2
+ shift
+ shift
+ case $mode in
+ *' '* | *' '* | *'
+'* | *'*'* | *'?'* | *'['*)
+ echo "$0: invalid mode: $mode" >&2
+ exit 1;;
+ esac
+ continue;;
+
+ -o) chowncmd="$chownprog $2"
+ shift
+ shift
+ continue;;
+
+ -s) stripcmd=$stripprog
+ shift
+ continue;;
+
+ -t) dstarg=$2
+ shift
+ shift
+ continue;;
+
+ -T) no_target_directory=true
+ shift
+ continue;;
+
+ --version) echo "$0 $scriptversion"; exit $?;;
+
+ --) shift
+ break;;
+
+ -*) echo "$0: invalid option: $1" >&2
+ exit 1;;
+
+ *) break;;
+ esac
+done
+
+if test $# -ne 0 && test -z "$dir_arg$dstarg"; then
+ # When -d is used, all remaining arguments are directories to create.
+ # When -t is used, the destination is already specified.
+ # Otherwise, the last argument is the destination. Remove it from $@.
+ for arg
+ do
+ if test -n "$dstarg"; then
+ # $@ is not empty: it contains at least $arg.
+ set fnord "$@" "$dstarg"
+ shift # fnord
+ fi
+ shift # arg
+ dstarg=$arg
+ done
+fi
+
+if test $# -eq 0; then
+ if test -z "$dir_arg"; then
+ echo "$0: no input file specified." >&2
+ exit 1
+ fi
+ # It's OK to call `install-sh -d' without argument.
+ # This can happen when creating conditional directories.
+ exit 0
+fi
+
+if test -z "$dir_arg"; then
+ trap '(exit $?); exit' 1 2 13 15
+
+ # Set umask so as not to create temps with too-generous modes.
+ # However, 'strip' requires both read and write access to temps.
+ case $mode in
+ # Optimize common cases.
+ *644) cp_umask=133;;
+ *755) cp_umask=22;;
+
+ *[0-7])
+ if test -z "$stripcmd"; then
+ u_plus_rw=
+ else
+ u_plus_rw='% 200'
+ fi
+ cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
+ *)
+ if test -z "$stripcmd"; then
+ u_plus_rw=
+ else
+ u_plus_rw=,u+rw
+ fi
+ cp_umask=$mode$u_plus_rw;;
+ esac
+fi
+
+for src
+do
+ # Protect names starting with `-'.
+ case $src in
+ -*) src=./$src ;;
+ esac
+
+ if test -n "$dir_arg"; then
+ dst=$src
+ dstdir=$dst
+ test -d "$dstdir"
+ dstdir_status=$?
+ else
+
+ # Waiting for this to be detected by the "$cpprog $src $dsttmp" command
+ # might cause directories to be created, which would be especially bad
+ # if $src (and thus $dsttmp) contains '*'.
+ if test ! -f "$src" && test ! -d "$src"; then
+ echo "$0: $src does not exist." >&2
+ exit 1
+ fi
+
+ if test -z "$dstarg"; then
+ echo "$0: no destination specified." >&2
+ exit 1
+ fi
+
+ dst=$dstarg
+ # Protect names starting with `-'.
+ case $dst in
+ -*) dst=./$dst ;;
+ esac
+
+ # If destination is a directory, append the input filename; won't work
+ # if double slashes aren't ignored.
+ if test -d "$dst"; then
+ if test -n "$no_target_directory"; then
+ echo "$0: $dstarg: Is a directory" >&2
+ exit 1
+ fi
+ dstdir=$dst
+ dst=$dstdir/`basename "$src"`
+ dstdir_status=0
+ else
+ # Prefer dirname, but fall back on a substitute if dirname fails.
+ dstdir=`
+ (dirname "$dst") 2>/dev/null ||
+ expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$dst" : 'X\(//\)[^/]' \| \
+ X"$dst" : 'X\(//\)$' \| \
+ X"$dst" : 'X\(/\)' \| . 2>/dev/null ||
+ echo X"$dst" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)[^/].*/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'
+ `
+
+ test -d "$dstdir"
+ dstdir_status=$?
+ fi
+ fi
+
+ obsolete_mkdir_used=false
+
+ if test $dstdir_status != 0; then
+ case $posix_mkdir in
+ '')
+ # Create intermediate dirs using mode 755 as modified by the umask.
+ # This is like FreeBSD 'install' as of 1997-10-28.
+ umask=`umask`
+ case $stripcmd.$umask in
+ # Optimize common cases.
+ *[2367][2367]) mkdir_umask=$umask;;
+ .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;;
+
+ *[0-7])
+ mkdir_umask=`expr $umask + 22 \
+ - $umask % 100 % 40 + $umask % 20 \
+ - $umask % 10 % 4 + $umask % 2
+ `;;
+ *) mkdir_umask=$umask,go-w;;
+ esac
+
+ # With -d, create the new directory with the user-specified mode.
+ # Otherwise, rely on $mkdir_umask.
+ if test -n "$dir_arg"; then
+ mkdir_mode=-m$mode
+ else
+ mkdir_mode=
+ fi
+
+ posix_mkdir=false
+ case $umask in
+ *[123567][0-7][0-7])
+ # POSIX mkdir -p sets u+wx bits regardless of umask, which
+ # is incompatible with FreeBSD 'install' when (umask & 300) != 0.
+ ;;
+ *)
+ tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
+ trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0
+
+ if (umask $mkdir_umask &&
+ exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1
+ then
+ if test -z "$dir_arg" || {
+ # Check for POSIX incompatibilities with -m.
+ # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
+ # other-writeable bit of parent directory when it shouldn't.
+ # FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
+ ls_ld_tmpdir=`ls -ld "$tmpdir"`
+ case $ls_ld_tmpdir in
+ d????-?r-*) different_mode=700;;
+ d????-?--*) different_mode=755;;
+ *) false;;
+ esac &&
+ $mkdirprog -m$different_mode -p -- "$tmpdir" && {
+ ls_ld_tmpdir_1=`ls -ld "$tmpdir"`
+ test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
+ }
+ }
+ then posix_mkdir=:
+ fi
+ rmdir "$tmpdir/d" "$tmpdir"
+ else
+ # Remove any dirs left behind by ancient mkdir implementations.
+ rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null
+ fi
+ trap '' 0;;
+ esac;;
+ esac
+
+ if
+ $posix_mkdir && (
+ umask $mkdir_umask &&
+ $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
+ )
+ then :
+ else
+
+ # The umask is ridiculous, or mkdir does not conform to POSIX,
+ # or it failed possibly due to a race condition. Create the
+ # directory the slow way, step by step, checking for races as we go.
+
+ case $dstdir in
+ /*) prefix=/ ;;
+ -*) prefix=./ ;;
+ *) prefix= ;;
+ esac
+
+ case $posix_glob in
+ '')
+ if (set -f) 2>/dev/null; then
+ posix_glob=true
+ else
+ posix_glob=false
+ fi ;;
+ esac
+
+ oIFS=$IFS
+ IFS=/
+ $posix_glob && set -f
+ set fnord $dstdir
+ shift
+ $posix_glob && set +f
+ IFS=$oIFS
+
+ prefixes=
+
+ for d
+ do
+ test -z "$d" && continue
+
+ prefix=$prefix$d
+ if test -d "$prefix"; then
+ prefixes=
+ else
+ if $posix_mkdir; then
+ (umask=$mkdir_umask &&
+ $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
+ # Don't fail if two instances are running concurrently.
+ test -d "$prefix" || exit 1
+ else
+ case $prefix in
+ *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
+ *) qprefix=$prefix;;
+ esac
+ prefixes="$prefixes '$qprefix'"
+ fi
+ fi
+ prefix=$prefix/
+ done
+
+ if test -n "$prefixes"; then
+ # Don't fail if two instances are running concurrently.
+ (umask $mkdir_umask &&
+ eval "\$doit_exec \$mkdirprog $prefixes") ||
+ test -d "$dstdir" || exit 1
+ obsolete_mkdir_used=true
+ fi
+ fi
+ fi
+
+ if test -n "$dir_arg"; then
+ { test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
+ { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
+ { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
+ test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
+ else
+
+ # Make a couple of temp file names in the proper directory.
+ dsttmp=$dstdir/_inst.$$_
+ rmtmp=$dstdir/_rm.$$_
+
+ # Trap to clean up those temp files at exit.
+ trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
+
+ # Copy the file name to the temp name.
+ (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") &&
+
+ # and set any options; do chmod last to preserve setuid bits.
+ #
+ # If any of these fail, we abort the whole thing. If we want to
+ # ignore errors from any of these, just make sure not to ignore
+ # errors from the above "$doit $cpprog $src $dsttmp" command.
+ #
+ { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } \
+ && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } \
+ && { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } \
+ && { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&
+
+ # Now rename the file to the real destination.
+ { $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null \
+ || {
+ # The rename failed, perhaps because mv can't rename something else
+ # to itself, or perhaps because mv is so ancient that it does not
+ # support -f.
+
+ # Now remove or move aside any old file at destination location.
+ # We try this two ways since rm can't unlink itself on some
+ # systems and the destination file might be busy for other
+ # reasons. In this case, the final cleanup might fail but the new
+ # file should still install successfully.
+ {
+ if test -f "$dst"; then
+ $doit $rmcmd -f "$dst" 2>/dev/null \
+ || { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null \
+ && { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; }; }\
+ || {
+ echo "$0: cannot unlink or rename $dst" >&2
+ (exit 1); exit 1
+ }
+ else
+ :
+ fi
+ } &&
+
+ # Now rename the file to the real destination.
+ $doit $mvcmd "$dsttmp" "$dst"
+ }
+ } || exit 1
+
+ trap '' 0
+ fi
+done
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-end: "$"
+# End:
diff --git a/interface.c b/interface.c
new file mode 100644
index 0000000..0351de5
--- /dev/null
+++ b/interface.c
@@ -0,0 +1,1895 @@
+/* **************************************************************** *
+ * *
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ * **************************************************************** */
+
+#include "aprx.h"
+#include <sys/socket.h>
+
+/*
+ * The interface subsystem describes all interfaces in one
+ * coherent way, independent of their actual implementation.
+ *
+ */
+
+/*
+
+<interface>
+ serial-device /dev/ttyUSB1 19200 8n1 KISS
+ tx-ok false # receive only (default)
+ callsign OH2XYZ-R2 # KISS subif 0
+ initstring "...." # initstring option
+ timeout 900 # 900 seconds of no Rx
+</interface>
+
+<interface>
+ serial-device /dev/ttyUSB2 19200 8n1 KISS
+ initstring "...."
+ timeout 900 # 900 seconds of no Rx
+ <kiss-subif 0>
+ callsign OH2XYZ-2
+ tx-ok true # This is our transmitter
+ </kiss-subif>
+ <kiss-subif 1>
+ callsign OH2XYZ-R3 # This is receiver
+ tx-ok false # receive only (default)
+ </kiss-subif>
+</interface>
+
+<interface>
+ tcp-device 172.168.1.1 4001 KISS
+ tx-ok false # receive only (default)
+ callsign OH2XYZ-R4 # KISS subif 0
+ initstring "...." # initstring option
+ timeout 900 # 900 seconds of no Rx
+</interface>
+
+<interface>
+ ax25-device OH2XYZ-6 # Works only on Linux systems
+ tx-ok true # This is also transmitter
+</interface>
+
+ */
+
+struct aprx_interface **all_interfaces;
+int all_interfaces_count;
+int top_interfaces_group;
+
+// Init-code stores this with ifindex = 0.
+// This is necessary even for system where igate is removed
+struct aprx_interface aprsis_interface = {
+ IFTYPE_APRSIS, 0, 0, 0, "APRSIS",
+ {'A'<<1,'P'<<1,'R'<<1,'S'<<1,'I'<<1,'S'<<1, 0x60},
+ 0, NULL,
+ 0, 0, 0, // subif, txrefcount, tx_ok
+ 1, 1, 0, // telemeter-to-is, telemeter-to-rf, telemeter-newformat
+ 0, NULL,
+ NULL,
+#ifdef ENABLE_AGWPE
+ NULL,
+#endif
+ NULL,
+ 0, NULL
+};
+
+int interface_is_beaconable(const struct aprx_interface *aif)
+{
+ switch (aif->iftype) {
+ case IFTYPE_AX25:
+ case IFTYPE_SERIAL:
+ case IFTYPE_TCPIP:
+ case IFTYPE_NULL:
+ case IFTYPE_APRSIS:
+ // case IFTYPE_AGWPE:
+ // These are beaconable.
+ return 1;
+
+ default:
+ break;
+ }
+ return 0;
+}
+
+int interface_is_telemetrable(const struct aprx_interface *aif) {
+ // Check if the interface type is really an RF rx and/or tx
+ switch (aif->iftype) {
+ case IFTYPE_AX25:
+ case IFTYPE_SERIAL:
+ case IFTYPE_TCPIP:
+ // case IFTYPE_AGWPE:
+ // These are real interfaces, and telemetry sources
+ return 1;
+
+ default:
+ break;
+ }
+ return 0;
+}
+
+#ifndef DISABLE_IGATE
+/*
+ * A helper for interface_receive_ax25() - analyze 3rd-party packets received
+ * via radio. If data content inside has path saying "TCPIP" or "TCPXX", consider
+ * the packet to be indication that fromcall is an IGate.
+ */
+static void rx_analyze_3rdparty( historydb_t *historydb, struct pbuf_t *pb )
+{
+ const char *e = pb->data + pb->packet_len - 6;
+ const char *p = pb->info_start;
+ int from_igate = 0;
+ history_cell_t *hist_rx;
+
+ if (!p) return; // Bad packet..
+ ++p;
+
+ for ( ; p < e; ++p ) {
+ if (*p == ':') break;
+ if (*p == ',') {
+ // The "TCPIP*" or "TCPXX*" will always have preceding ","
+ if (memcmp(",TCPIP*", p, 7) == 0) {
+ from_igate = 1;
+ break;
+ }
+ if (memcmp(",TCPXX*", p, 7) == 0) {
+ from_igate = 1;
+ break;
+ }
+ } // Start with 'T'.
+ }
+
+ if (!from_igate) return; // Not recognized as being sent from another TX-IGATE
+
+ // OK, this packet originated from an TX-IGATE
+
+ // Insert it afresh
+ hist_rx = historydb_insert_heard(historydb, pb);
+ if (hist_rx != NULL) {
+ // Explicitly mark it as "received from APRSIS"
+ // The packet was received from a TX-IGATE, therefore
+ // the source of that packet is now logged as "from APRSIS".
+ hist_rx->last_heard[0] = pb->t;
+ }
+}
+#endif
+
+static char *interface_default_aliases[] = { "RELAY","WIDE","TRACE" };
+
+static void interface_store(struct aprx_interface *aif)
+{
+ if (debug)
+ printf("interface_store() aif->callsign = '%s'\n", aif->callsign);
+
+ // Init the interface specific Erlang accounting
+ erlang_add(aif->callsign, ERLANG_RX, 0, 0);
+
+ all_interfaces_count += 1;
+ all_interfaces = realloc(all_interfaces,
+ sizeof(*all_interfaces) * all_interfaces_count);
+ all_interfaces[all_interfaces_count -1] = aif;
+ if (aif->ifindex < 0)
+ aif->ifindex = all_interfaces_count -1;
+ if (aif->ifgroup < 0) {
+ aif->ifgroup = all_interfaces_count; // starting at 1. the 0 is for APRSIS
+ /* -- no hard upper limit anymore
+ if (aif->ifgroup >= MAX_IF_GROUP)
+ aif->ifgroup = MAX_IF_GROUP -1;
+ */
+ }
+ if (top_interfaces_group <= aif->ifgroup)
+ top_interfaces_group = aif->ifgroup +1;
+}
+
+struct aprx_interface *find_interface_by_callsign(const char *callsign)
+{
+ int i;
+ for (i = 0; i < all_interfaces_count; ++i) {
+ if ((all_interfaces[i]->callsign != NULL) &&
+ (strcasecmp(callsign, all_interfaces[i]->callsign) == 0)) {
+ return all_interfaces[i];
+ }
+ }
+ return NULL; // Not found!
+}
+
+struct aprx_interface *find_interface_by_index(const int index)
+{
+ if (index >= all_interfaces_count ||
+ index < 0) {
+ return NULL; // Invalid index value
+ } else {
+ return all_interfaces[index];
+ }
+}
+
+static int config_kiss_subif(struct configfile *cf, struct aprx_interface *aifp, char *param1, char *str, int maxsubif)
+{
+ struct aprx_interface *aif;
+ int fail = 0;
+
+ char *name;
+ int parlen = 0;
+
+ char *initstring = NULL;
+ int initlength = 0;
+ char *callsign = NULL;
+ int subif = 0;
+ int tx_ok = 0;
+ int telemeter_to_is = 1;
+ int telemeter_to_rf = 1;
+ int aliascount = 0;
+ char **aliases = NULL;
+ int ifgroup = -1;
+
+ const char *p = param1;
+ int c;
+
+ if (aifp == NULL || aifp->tty == NULL) {
+ printf("%s:%d ERROR: <kiss-subif> on bad type of <interface> entry.\n",
+ cf->name, cf->linenum);
+ return 1;
+
+ }
+
+ for ( ; *p; ++p ) {
+ c = *p;
+ if ('0' <= c && c <= '9') {
+ subif = subif * 10 + (c - '0');
+ } else if (c == '>') {
+ // all fine..
+ break;
+ } else {
+ // FIXME: <KISS-SubIF nnn> parameter value is bad!
+ printf("%s:%d ERROR: <kiss-subif %s parameter value is bad\n",
+ cf->name, cf->linenum, param1);
+ return 1;
+ }
+ }
+ if (subif >= maxsubif) {
+ // FIXME: <KISS-SubIF nnn> parameter value is bad!
+ printf("%s:%d ERROR: <kiss-subif %s parameter value is too large for this KISS variant.\n",
+ cf->name, cf->linenum, param1);
+ return 1;
+ }
+
+ while (readconfigline(cf) != NULL) {
+ if (configline_is_comment(cf))
+ continue; /* Comment line, or empty line */
+
+ // It can be severely indented...
+ str = config_SKIPSPACE(cf->buf);
+
+ name = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+ config_STRLOWER(name);
+
+ param1 = str;
+ str = config_SKIPTEXT(str, &parlen);
+ str = config_SKIPSPACE(str);
+
+ if (strcmp(name, "</kiss-subif>") == 0) {
+ break; // End of this sub-group
+ }
+
+ if (strcmp(name, "callsign") == 0) {
+
+ if (strcasecmp(param1,"$mycall") == 0)
+ callsign = strdup(mycall);
+ else
+ callsign = strdup(param1);
+
+ if (!validate_callsign_input(callsign,tx_ok)) {
+ if (tx_ok)
+ printf("%s:%d ERROR: The CALLSIGN parameter on AX25-DEVICE must be of valid AX.25 format! '%s'\n",
+ cf->name, cf->linenum, callsign);
+ else
+ printf("%s:%d ERROR: The CALLSIGN parameter on AX25-DEVICE must be of valid APRSIS format! '%s'\n",
+ cf->name, cf->linenum, callsign);
+ fail = 1;
+ break;
+ }
+
+ if (find_interface_by_callsign(callsign) != NULL) {
+ // An interface with THIS callsign does exist already!
+ printf("%s:%d ERROR: Same callsign (%s) exists already on another interface.\n",
+ cf->name, cf->linenum, callsign);
+ fail = 1;
+ continue;
+ }
+
+ } else if (strcmp(name, "initstring") == 0) {
+
+ if (initstring == NULL) {
+ initlength = parlen;
+ initstring = malloc(parlen);
+ memcpy(initstring, param1, parlen);
+ } else {
+ printf("%s:%d ERROR: Double-definition of initstring parameter.\n",
+ cf->name, cf->linenum);
+ fail = 1;
+ break;
+ }
+
+ } else if (strcmp(name, "tx-ok") == 0) {
+ if (!config_parse_boolean(param1, &tx_ok)) {
+ printf("%s:%d ERROR: Bad TX-OK parameter value -- not a recognized boolean: %s\n",
+ cf->name, cf->linenum, param1);
+ fail = 1;
+ break;
+ }
+
+ } else if (strcmp(name, "telem-to-is") == 0) {
+ if (!config_parse_boolean(param1, &telemeter_to_is)) {
+ printf("%s:%d ERROR: Bad TELEM-TO-IS parameter value -- not a recognized boolean: %s\n",
+ cf->name, cf->linenum, param1);
+ fail = 1;
+ break;
+ }
+
+ } else if (strcmp(name, "telem-to-rf") == 0) {
+ if (!config_parse_boolean(param1, &telemeter_to_rf)) {
+ printf("%s:%d ERROR: Bad TELEM-TO-RF parameter value -- not a recognized boolean: %s\n",
+ cf->name, cf->linenum, param1);
+ fail = 1;
+ break;
+ }
+
+ } else if (strcmp(name, "alias") == 0) {
+ char *k = strtok(param1, ",");
+ for (; k ; k = strtok(NULL,",")) {
+ ++aliascount;
+ if (debug) printf(" n=%d alias='%s'\n",aliascount,k);
+ aliases = realloc(aliases, sizeof(char*) * aliascount);
+ aliases[aliascount-1] = strdup(k);
+ }
+
+#ifndef DISABLE_IGATE
+ } else if (strcmp(name, "igate-group") == 0) {
+ // param1 = integer 1 to N.
+ ifgroup = atol(param1);
+ if (ifgroup < 1) {
+ printf("%s:%d ERROR: interface 'igate-group' parameter value: '%s' is an integer with minimum value of 1.\n",
+ cf->name, cf->linenum, param1);
+ fail = 1;
+ break;
+ /* -- no hard upper limit anymore
+ } else if (ifgroup >= MAX_IF_GROUP) {
+ printf("%s:%d ERROR: interface 'igate-group' parameter value: '%s' is an integer with maximum value of %d.\n",
+ cf->name, cf->linenum, param1, MAX_IF_GROUP-1);
+ fail = 1;
+ break;
+ */
+ }
+#endif
+
+ } else {
+ printf("%s:%d ERROR: Unrecognized <interface> block keyword: %s\n",
+ cf->name, cf->linenum, name);
+ fail = 1;
+ break;
+ }
+ }
+ if (fail) {
+ ERRORMEMFREE:
+ if (aliases != NULL) free(aliases);
+ if (initstring != NULL) free(initstring);
+ return 1; // this leaks memory (but also diagnoses bad input)
+ }
+
+ if (callsign == NULL) {
+ // FIXME: Must define at least a callsign!
+ printf("%s:%d ERROR: <kiss-subif ..> MUST define CALLSIGN parameter!\n",
+ cf->name, cf->linenum);
+ goto ERRORMEMFREE;
+ }
+
+ if (find_interface_by_callsign(callsign) != NULL) {
+ // An interface with THIS callsign does exist already!
+ printf("%s:%d ERROR: Same callsign (%s) exists already on another interface.\n",
+ cf->name, cf->linenum, callsign);
+ goto ERRORMEMFREE;
+ }
+
+ if (debug)
+ printf(" Defining <kiss-subif %d> callsign=%s txok=%s\n", subif, callsign,
+ tx_ok ? "true":"false");
+
+
+ aif = malloc(sizeof(*aif));
+ memcpy(aif, aifp, sizeof(*aif));
+
+ aif->callsign = callsign;
+ parse_ax25addr(aif->ax25call, callsign, 0x60);
+ aif->subif = subif;
+ aif->tx_ok = tx_ok;
+ aif->telemeter_to_is = telemeter_to_is;
+ aif->telemeter_to_rf = telemeter_to_rf;
+ // aif->telemeter_newformat = ...
+ aif->ifindex = -1; // system sets automatically at store time
+ aif->ifgroup = ifgroup; // either user sets, or system sets at store time
+
+ aifp->tty->interface [subif] = aif;
+ aifp->tty->ttycallsign[subif] = callsign;
+#ifdef PF_AX25 /* PF_AX25 exists -- highly likely a Linux system ! */
+ aifp->tty->netax25 [subif] = netax25_open(callsign);
+#endif
+ if (initstring != NULL) {
+ aifp->tty->initlen[subif] = initlength;
+ aifp->tty->initstring[subif] = initstring;
+ }
+
+ if (aliascount == 0 || aliases == NULL) {
+ aif->aliascount = 3;
+ aif->aliases = interface_default_aliases;
+ } else {
+ aif->aliascount = aliascount;
+ aif->aliases = aliases;
+ }
+
+ return 0;
+}
+
+void interface_init()
+{
+ interface_store( &aprsis_interface );
+}
+
+int interface_config(struct configfile *cf)
+{
+ struct aprx_interface *aif = calloc(1, sizeof(*aif));
+
+ char *name, *param1;
+ char *str = cf->buf;
+ int parlen = 0;
+ int have_fault = 0;
+ int maxsubif = 16; // 16 for most KISS modes, 8 for SMACK
+ int defined_subinterface_count = 0;
+ int ifgroup = -1;
+
+ aif->iftype = IFTYPE_UNSET;
+ aif->aliascount = 3;
+ aif->aliases = interface_default_aliases;
+ aif->ifindex = -1; // system sets automatically at store time
+ aif->ifgroup = ifgroup; // either user sets, or system sets at store time
+ aif->tx_ok = 0;
+ aif->telemeter_to_is = 1;
+ aif->telemeter_to_rf = 1;
+ aif->telemeter_newformat = 0;
+
+ while (readconfigline(cf) != NULL) {
+ if (configline_is_comment(cf))
+ continue; /* Comment line, or empty line */
+
+ // It can be severely indented...
+ str = config_SKIPSPACE(cf->buf);
+
+ name = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+ config_STRLOWER(name);
+
+ param1 = str;
+ str = config_SKIPTEXT(str, &parlen);
+ str = config_SKIPSPACE(str);
+
+ if (strcmp(name, "</interface>") == 0) {
+ // End of this interface definition
+
+ // make the interface...
+
+ break;
+ }
+ if (strcmp(name, "<kiss-subif") == 0) {
+ if (config_kiss_subif(cf, aif, param1, str, maxsubif)) {
+ // Bad inputs.. complained already
+ have_fault = 1;
+ }
+ // Always count as defined, even when an error happened!
+ ++defined_subinterface_count;
+
+ continue;
+ }
+
+ // Interface parameters
+
+ if (strcmp(name,"ax25-device") == 0) {
+#ifdef PF_AX25 // PF_AX25 exists -- highly likely a Linux system !
+ if (aif->iftype == IFTYPE_UNSET) {
+ aif->iftype = IFTYPE_AX25;
+ // aif->nax25p = NULL;
+ } else {
+ printf("%s:%d ERROR: Only single device specification per interface block!\n",
+ cf->name, cf->linenum);
+ have_fault = 1;
+ continue;
+ }
+
+ if (strcasecmp(param1,"$mycall") == 0)
+ param1 = strdup(mycall);
+
+ if (!validate_callsign_input(param1,1)) {
+ printf("%s:%d ERROR: The CALLSIGN parameter on AX25-DEVICE must be of valid AX.25 format! '%s'\n",
+ cf->name, cf->linenum, param1);
+ have_fault = 1;
+ continue;
+ }
+
+ if (find_interface_by_callsign(param1) != NULL) {
+ // An interface with THIS callsign does exist already!
+ printf("%s:%d ERROR: Same callsign (%s) exists already on another interface.\n",
+ cf->name, cf->linenum, param1);
+ have_fault = 1;
+ continue;
+ }
+
+ if (debug)
+ printf("%s:%d: AX25-DEVICE '%s' '%s'\n",
+ cf->name, cf->linenum, param1, str);
+
+ aif->callsign = strdup(param1);
+ parse_ax25addr(aif->ax25call, aif->callsign, 0x60);
+ aif->nax25p = netax25_addrxport(param1, aif);
+ if (aif->nax25p == NULL) {
+ printf("%s:%d ERROR: Failed to open this AX25-DEVICE: '%s'\n",
+ cf->name, cf->linenum, param1);
+ have_fault = 1;
+ continue;
+ }
+#else
+ printf("%s:%d ERROR: AX25-DEVICE interfaces are not supported at this system!\n",
+ cf->name, cf->linenum, param1);
+ have_fault = 1;
+#endif
+
+ } else if ((strcmp(name,"serial-device") == 0) && (aif->tty == NULL)) {
+
+ if (aif->iftype == IFTYPE_UNSET) {
+ aif->iftype = IFTYPE_SERIAL;
+ aif->tty = ttyreader_new();
+ aif->tty->ttyname = strdup(param1);
+ aif->tty->interface[0] = aif;
+ aif->tty->ttycallsign[0] = mycall;
+
+ // end processing registers it
+
+ } else {
+ printf("%s:%d ERROR: Only single device specification per interface block!\n",
+ cf->name, cf->linenum);
+ have_fault = 1;
+ continue;
+ }
+
+ if (debug)
+ printf(".. new style serial: '%s' '%s'.. tncid=0\n",
+ aif->tty->ttyname, str);
+
+ have_fault |= ttyreader_parse_ttyparams(cf, aif->tty, str);
+
+ switch (aif->tty->linetype) {
+ case LINETYPE_KISSSMACK:
+ maxsubif = 8; // 16 for most KISS modes, 8 for SMACK
+ break;
+ case LINETYPE_KISSFLEXNET:
+ // ???
+ break;
+ default:
+ break;
+ }
+
+ // Always count as defined, even when an error happened!
+ ++defined_subinterface_count;
+
+ } else if ((strcmp(name,"tcp-device") == 0) && (aif->tty == NULL)) {
+ int len;
+ char *host, *port;
+
+ if (aif->iftype == IFTYPE_UNSET) {
+ aif->iftype = IFTYPE_TCPIP;
+ aif->tty = ttyreader_new();
+ aif->tty->interface[0] = aif;
+ aif->tty->ttycallsign[0] = mycall;
+
+ // end-step processing registers it
+
+ } else {
+ printf("%s:%d ERROR: Only single device specification per interface block!\n",
+ cf->name, cf->linenum);
+ have_fault = 1;
+ continue;
+ }
+
+ host = param1;
+
+ port = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ if (debug)
+ printf(".. new style tcp!: '%s' '%s' '%s'..\n",
+ host, port, str);
+
+ len = strlen(host) + strlen(port) + 8;
+
+ aif->tty->ttyname = malloc(len);
+ sprintf((char *) (aif->tty->ttyname), "tcp!%s!%s!", host, port);
+
+ have_fault |= ttyreader_parse_ttyparams( cf, aif->tty, str );
+
+ switch (aif->tty->linetype) {
+ case LINETYPE_KISSSMACK:
+ maxsubif = 8; // 16 for most KISS modes, 8 for SMACK
+ break;
+ case LINETYPE_KISSFLEXNET:
+ // ???
+ break;
+ default:
+ break;
+ }
+
+ // Always count as defined, even when an error happened!
+ ++defined_subinterface_count;
+
+ } else if (strcmp(name,"null-device") == 0) {
+ if (aif->iftype == IFTYPE_UNSET) {
+ aif->iftype = IFTYPE_NULL;
+ // aif->nax25p = NULL;
+ } else {
+ printf("%s:%d ERROR: Only single device specification per interface block!\n",
+ cf->name, cf->linenum);
+ have_fault = 1;
+ continue;
+ }
+ aif->tx_ok = 1;
+
+ if (strcasecmp(param1,"$mycall") == 0)
+ param1 = strdup(mycall);
+
+ if (find_interface_by_callsign(param1) != NULL) {
+ // An interface with THIS callsign does exist already!
+ printf("%s:%d ERROR: Same callsign (%s) exists already on another interface.\n",
+ cf->name, cf->linenum, param1);
+ have_fault = 1;
+ continue;
+ }
+
+ if (!have_fault) {
+ aif->iftype = IFTYPE_TCPIP;
+ aif->tty = ttyreader_new();
+ aif->tty->interface[0] = aif;
+ aif->tty->ttycallsign[0] = mycall;
+ }
+ have_fault |= ttyreader_parse_nullparams(cf, aif->tty, str);
+
+
+ if (debug)
+ printf("%s:%d: NULL-DEVICE '%s' '%s'\n",
+ cf->name, cf->linenum, param1, str);
+
+ aif->callsign = strdup(param1);
+ parse_ax25addr(aif->ax25call, aif->callsign, 0x60);
+
+
+#ifdef ENABLE_AGWPE
+ } else if ((strcmp(name,"agwpe-device") == 0) && (aif->tty == NULL)) {
+
+ // agwpe-device hostname hostport callsign agwpeportnum
+
+ int len;
+ const char *hostname, *hostport;
+ char *callsign, *agwpeportnum;
+
+ if (aif->iftype == IFTYPE_UNSET) {
+ aif->iftype = IFTYPE_AGWPE;
+ aif->tty = ttyreader_new();
+ aif->tty->interface[0] = aif;
+ aif->tty->ttycallsign[0] = mycall;
+
+ // end-step processing registers it
+
+ } else {
+ printf("%s:%d ERROR: Only single device specification per interface block!\n",
+ cf->name, cf->linenum);
+ have_fault = 1;
+ continue;
+ }
+
+ hostname = strdup(param1);
+ hostport = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+ hostport = strdup(hostport);
+
+ callsign = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ agwpeportnum = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ if (debug)
+ printf(".. AGWPE-DEVICE: '%s' '%s' '%s' '%s' ('%s'...)\n",
+ hostname, hostport, callsign, agwpeportnum, str);
+
+ len = strlen(hostname) + strlen(hostport) + strlen(agwpeportnum) + 8;
+ aif->tty->ttyname = malloc(len);
+ sprintf((char *) (aif->tty->ttyname), "tcp!%s!%s[%s]",
+ hostname, hostport, agwpeportnum);
+
+
+ if (strcasecmp(callsign,"$mycall") == 0)
+ callsign = strdup(mycall);
+ else
+ callsign = strdup(callsign);
+
+ if (!validate_callsign_input(callsign,1)) {
+ printf("%s:%d ERROR: The CALLSIGN parameter on AGWPE-DEVICE must be of valid AX.25 format! '%s'\n",
+ cf->name, cf->linenum, callsign);
+ have_fault = 1;
+ continue;
+ }
+
+ if (find_interface_by_callsign(callsign) != NULL) {
+ // An interface with THIS callsign does exist already!
+ printf("%s:%d ERROR: Same callsign (%s) exists already on another interface.\n",
+ cf->name, cf->linenum, callsign);
+ have_fault = 1;
+ continue;
+ }
+
+ aif->callsign = callsign;
+ parse_ax25addr(aif->ax25call, aif->callsign, 0x60);
+ aif->agwpe = agwpe_addport(hostname, hostport, agwpeportnum, aif);
+ if (aif->agwpe == NULL) {
+ printf("%s:%d ERROR: Failed to setup this AGWPE-DEVICE: '%s'\n",
+ cf->name, cf->linenum, callsign);
+ have_fault = 1;
+ continue;
+ }
+
+ // Always count as defined, even when an error happened!
+ ++defined_subinterface_count;
+#endif
+ } else if (strcmp(name,"tx-ok") == 0) {
+
+ int bool;
+ if (!config_parse_boolean(param1, &bool)) {
+ printf("%s:%d ERROR: Bad TX-OK parameter value -- not a recognized boolean: %s\n",
+ cf->name, cf->linenum, param1);
+ have_fault = 1;
+ continue;
+ }
+ aif->tx_ok = bool;
+ if (bool && aif->callsign) {
+ if (!validate_callsign_input(aif->callsign,bool)) { // Transmitters REQUIRE valid AX.25 address
+ printf("%s:%d: ERROR: TX-OK 'TRUE' -- BUT PREVIOUSLY SET CALLSIGN IS NOT VALID AX.25 ADDRESS \n",
+ cf->name, cf->linenum);
+ continue;
+ }
+ }
+
+ } else if (strcmp(name, "telem-to-is") == 0) {
+ int bool;
+ if (!config_parse_boolean(param1, &bool)) {
+ printf("%s:%d ERROR: Bad TELEM-TO-IS parameter value -- not a recognized boolean: %s\n",
+ cf->name, cf->linenum, param1);
+ have_fault = 1;
+ break;
+ }
+ aif->telemeter_to_is = bool;
+
+ } else if (strcmp(name, "telem-to-rf") == 0) {
+ int bool;
+ if (!config_parse_boolean(param1, &bool)) {
+ printf("%s:%d ERROR: Bad TELEM-TO-RF parameter value -- not a recognized boolean: %s\n",
+ cf->name, cf->linenum, param1);
+ have_fault = 1;
+ break;
+ }
+ aif->telemeter_to_rf = bool;
+
+ } else if (strcmp(name,"timeout") == 0) {
+ if (config_parse_interval(param1, &(aif->timeout) ) ||
+ (aif->timeout < 0) || (aif->timeout > 1200)) {
+ aif->timeout = 0;
+ printf("%s:%d ERROR: Bad TIMEOUT parameter value: '%s' accepted range: 0 to 1200 seconds.\n",
+ cf->name, cf->linenum, param1);
+ have_fault = 1;
+ continue;
+ }
+ if (aif->tty != NULL) {
+ aif->tty->read_timeout = aif->timeout;
+ }
+
+ } else if (strcmp(name, "callsign") == 0) {
+ if (strcasecmp(param1,"$mycall") == 0)
+ param1 = strdup(mycall);
+
+ if (find_interface_by_callsign(param1) != NULL) {
+ // An interface with THIS callsign does exist already!
+ printf("%s:%d ERROR: Same callsign (%s) exists already on another interface.\n",
+ cf->name, cf->linenum, param1);
+ have_fault = 1;
+ continue;
+ }
+
+ if (!validate_callsign_input(param1, aif->tx_ok)) {
+ if (aif->tx_ok && aif->iftype != IFTYPE_NULL) {
+ printf("%s:%d ERROR: The CALLSIGN parameter on transmit capable interface must be of valid AX.25 format! '%s'\n",
+ cf->name, cf->linenum, param1);
+ have_fault = 1;
+ continue;
+ }
+ }
+
+ if (aif->callsign != NULL)
+ free(aif->callsign);
+ aif->callsign = strdup(param1);
+ parse_ax25addr(aif->ax25call, aif->callsign, 0x60);
+ if (aif->tty != NULL)
+ aif->tty->ttycallsign[0] = aif->callsign;
+
+ if (debug)
+ printf(" callsign= '%s'\n", aif->callsign);
+
+ } else if (strcmp(name, "initstring") == 0) {
+
+ if (aif->tty != NULL) {
+ int initlength = parlen;
+ char *initstring = malloc(parlen);
+ memcpy(initstring, param1, parlen);
+ aif->tty->initstring[0] = initstring;
+ aif->tty->initlen[0] = initlength;
+ }
+
+ } else if (strcmp(name, "alias") == 0) {
+ char *k = strtok(param1, ",");
+ if (aif->aliases == interface_default_aliases) {
+ aif->aliascount = 0;
+ aif->aliases = NULL;
+ }
+ for (; k ; k = strtok(NULL,",")) {
+ aif->aliascount += 1;
+ if (debug) printf(" n=%d alias='%s'\n",aif->aliascount,k);
+ aif->aliases = realloc(aif->aliases, sizeof(char*) * aif->aliascount);
+ aif->aliases[aif->aliascount-1] = strdup(k);
+ }
+
+#ifndef DISABLE_IGATE
+ } else if (strcmp(name, "igate-group") == 0) {
+ // param1 = integer 1 to N.
+ ifgroup = atol(param1);
+ if (ifgroup < 1) {
+ printf("%s:%d ERROR: interface 'igate-group' parameter value: '%s' is an integer with minimum value of 1.\n",
+ cf->name, cf->linenum, param1);
+ have_fault = 1;
+ continue;
+ /* -- no hard upper limit anymore
+ } else if (ifgroup >= MAX_IF_GROUP) {
+ printf("%s:%d ERROR: interface 'igate-group' parameter value: '%s' is an integer with maximum value of %d.\n",
+ cf->name, cf->linenum, param1, MAX_IF_GROUP-1);
+ have_fault = 1;
+ continue;
+ */
+ }
+#endif
+
+ } else {
+ printf("%s:%d ERROR: Unknown <interface> config entry name: '%s'\n",
+ cf->name, cf->linenum, name);
+ have_fault = 1;
+ }
+ }
+
+
+ while (!have_fault &&
+ aif->callsign == NULL &&
+ (aif->iftype == IFTYPE_SERIAL || aif->iftype == IFTYPE_TCPIP) &&
+ defined_subinterface_count == 1) {
+
+ // First check if there already is an interface with $mycall
+ // callsign on it..
+
+ if (find_interface_by_callsign(mycall) != NULL) {
+ // An interface with $MYCALL callsign does exist already!
+ printf("%s:%d ERROR: The $MYCALL callsign (%s) exists already on another interface.\n",
+ cf->name, cf->linenum, mycall);
+ have_fault = 1;
+ break;
+ }
+
+ // Supply a default value
+ aif->callsign = strdup(mycall);
+ parse_ax25addr(aif->ax25call, aif->callsign, 0x60);
+
+#ifdef PF_AX25 // PF_AX25 exists -- highly likely a Linux system !
+ // With enough defaults being used, the callsign is defined
+ // by global "macro" mycall, and never ends up activating
+ // the tty -> linux kernel kiss/smack pty interface.
+ // This part does that final step for minimalistic config.
+ if (aif->tty != NULL &&
+ aif->tty->netax25[0] == NULL &&
+ aif->tty->ttycallsign[0] != NULL) {
+ aif->tty->netax25[0]
+ = netax25_open(aif->tty->ttycallsign[0]);
+ }
+#endif
+ // Done it, leave..
+ break;
+ }
+
+ if (!have_fault) {
+ int i;
+
+ if (aif->tty != NULL) {
+ // Register all tty subinterfaces
+ if (debug) printf(" .. store tty subinterfaces\n");
+ for (i = 0; i < maxsubif; ++i) {
+ if (aif->tty->interface[i] != NULL) {
+ if (debug) printf(" .. store interface[%d] callsign='%s'\n",i, aif->tty->interface[i]->callsign);
+ interface_store(aif->tty->interface[i]);
+ }
+ }
+ } else {
+ // Not TTY multiplexed ( = KISS ) interface,
+ // register just the primary.
+ aif->ifgroup = ifgroup; // either user sets, or system sets at store time
+ interface_store(aif);
+
+ if (debug) printf(" .. store other interface\n");
+
+ }
+
+ if (aif->iftype == IFTYPE_SERIAL)
+ ttyreader_register(aif->tty);
+
+ if (aif->iftype == IFTYPE_TCPIP)
+ ttyreader_register(aif->tty);
+
+ } else {
+ if (aif->callsign) free(aif->callsign);
+ if (aif->tty) {
+ if (aif->tty->ttyname) free((void*)(aif->tty->ttyname));
+ }
+
+ free(aif);
+ }
+
+ // coverity[leaked_storage]
+ return have_fault;
+}
+
+
+/*
+ * Process received AX.25 packet
+ * - from AIF do find all DIGIPEATERS wanting this source.
+ * - If there are none, end processing.
+ * - Parse the received frame for possible latter filters
+ * - Feed the resulting parsed packet to each digipeater
+ *
+ *
+ * Tx-IGate rules:
+ *
+ // 2) - sending station has not been heard recently
+ // on radio
+ // 1) - verify receiving station has been heard
+ // recently on radio
+ // 4) - the receiving station has not been heard via
+ // the Internet within a predefined time period.
+ // (Note that _this_ packet is heard from internet,
+ // so one must not confuse this to history..
+ // Nor this siblings that are being created
+ // one for each tx-interface...)
+ //
+ // A station is said to be heard via the Internet if packets
+ // from the station contain TCPIP* or TCPXX* in the header or
+ // if gated (3rd-party) packets are seen on RF gated by the
+ // station and containing TCPIP or TCPXX in the 3rd-party
+ // header (in other words, the station is seen on RF as being
+ // an IGate).
+ *
+ * That is, this part of code collects knowledge of RF-wise near-by TX-IGATEs.
+ */
+
+void interface_receive_ax25(const struct aprx_interface *aif,
+ const char *ifaddress, const int is_aprs, const int ui_pid,
+ const uint8_t *axbuf, const int axaddrlen, const int axlen,
+ const char *tnc2buf, const int tnc2addrlen, const int tnc2len)
+{
+ int i;
+ int digi_like_aprs = is_aprs;
+
+ if (aif == NULL) return; // Not a real interface for digi use
+ if (aif->digisourcecount == 0) {
+ if (debug>1) printf("interface_receive_ax25() no receivers for source %s\n",aif->callsign);
+ return; // No receivers for this source
+ }
+
+ if (debug) printf("interface_receive_ax25() from %s axlen=%d tnc2len=%d\n",aif->callsign,axlen,tnc2len);
+
+
+ if (axaddrlen <= 14) return; // SOURCE>DEST without any VIAs..
+ // Note: Above one disables MICe destaddress-SSID embedded
+ // extremely compressed WIDEn-N notation.
+
+// FIXME: match ui_pid to list of UI PIDs that are treated with similar
+// digipeat rules as is APRS New-N.
+
+ // ui_pid < 0 means that this frame is not an UI frame at all.
+ if (ui_pid >= 0) digi_like_aprs = 1; // FIXME: more precise matching?
+
+
+ for (i = 0; i < aif->digisourcecount; ++i) {
+ struct digipeater_source *digisource = aif->digisources[i];
+#ifndef DISABLE_IGATE
+ // Transmitter's HistoryDB
+ historydb_t *historydb = digisource->parent->historydb;
+#endif
+
+ // Allocate pbuf, it is born "gotten" (refcount == 1)
+ struct pbuf_t *pb = pbuf_new(is_aprs, digi_like_aprs,
+ tnc2addrlen, tnc2buf, tnc2len,
+ axaddrlen, axbuf, axlen);
+ if (pb == NULL) {
+ // Urgh! Can't do a thing to this!
+ // Likely reason: axlen+tnc2len > 2100 bytes!
+ continue;
+ }
+
+ pb->source_if_group = aif->ifgroup;
+
+ // If APRS packet, then parse for APRS meaning ...
+ if (is_aprs) {
+ int rc = parse_aprs(pb,
+#ifndef DISABLE_IGATE
+ historydb
+#else
+ NULL
+#endif
+ ); // don't look inside 3rd party
+ char *srcif = aif->callsign;
+ if (debug)
+ printf(".. parse_aprs() rc=%s type=0x%02x srcif=%s tnc2addr='%s' info_start='%s'\n",
+ rc ? "OK":"FAIL", pb->packettype, srcif, pb->data, pb->info_start);
+
+ // If there are no filters, permit all packets
+ if (digisource->src_filters != NULL) {
+ int filter_discard =
+ filter_process(pb,
+ digisource->src_filters,
+#ifndef DISABLE_IGATE
+ historydb // Transmitter HistoryDB
+#else
+ NULL
+#endif
+ );
+ // filter_discard > 0: accept
+ // filter_discard = 0: indifferent (not reject, not accept), tx-igate rules as is.
+ // filter_discard < 0: reject
+ if (debug)
+ printf("source filtering result: %s\n",
+ (filter_discard < 0 ? "DISCARD" :
+ (filter_discard > 0 ? "ACCEPT" : "no-match")));
+
+ if (filter_discard <= 0) {
+ pbuf_put(pb);
+ continue; // allow only explicitly accepted
+ }
+ }
+
+#ifndef DISABLE_IGATE
+ // Find out IGATE callsign (if any), and record it on transmitter's historydb.
+ if (pb->packettype & T_THIRDPARTY) {
+ rx_analyze_3rdparty( historydb, pb );
+ } else {
+ // Everything else, feed to history-db
+ historydb_insert_heard( historydb, pb );
+ }
+#endif
+ }
+
+ // Feed it to digipeater ...
+ digipeater_receive( digisource, pb);
+
+ // .. and finally free up the pbuf (if refcount goes to zero)
+ pbuf_put(pb);
+ }
+}
+
+
+/*
+ * Process AX.25 packet transmit; beacons, digi output, igate output...
+ *
+ * - aif: output interface
+ * - axaddr: ax.25 address
+ * - axdata: payload content, with control and PID bytes prefixing them
+ */
+
+void interface_transmit_ax25(const struct aprx_interface *aif, uint8_t *axaddr, const int axaddrlen, const char *axdata, const int axdatalen)
+{
+ int axlen = axaddrlen + axdatalen;
+ uint8_t *axbuf;
+
+ if (debug) {
+ const char *callsign = "";
+ if (aif != NULL) callsign=aif->callsign;
+ printf("interface_transmit_ax25(aif=%p[%s], .., axlen=%d)\n",
+ aif, callsign, axlen);
+ }
+ if (axlen == 0) return;
+ if (aif == NULL) return;
+
+
+ switch (aif->iftype) {
+ case IFTYPE_SERIAL:
+ case IFTYPE_TCPIP:
+ // If there is linetype error, kisswrite detects it.
+ // Make it into single buffer to give to KISS sender
+ if (debug>2) {
+ printf("serial_sendto() len=%d,%d: ",axaddrlen,axdatalen);
+ hexdumpfp(stdout, axaddr, axaddrlen, 1);
+ printf(" // ");
+ hexdumpfp(stdout, (uint8_t*)axdata, axdatalen, 0);
+ printf("\n");
+ }
+
+ axbuf = alloca(axlen);
+ memcpy(axbuf, axaddr, axaddrlen);
+ memcpy(axbuf + axaddrlen, axdata, axdatalen);
+ kiss_kisswrite(aif->tty, aif->subif, axbuf, axlen);
+ break;
+#ifdef PF_AX25 /* PF_AX25 exists -- highly likely a Linux system ! */
+ case IFTYPE_AX25:
+ // The Linux netax25 sender takes same data as this interface
+ netax25_sendto( aif->nax25p,
+ axaddr, axaddrlen,
+ axdata, axdatalen );
+ break;
+#endif
+#ifdef ENABLE_AGWPE
+ case IFTYPE_AGWPE:
+ agwpe_sendto( aif->agwpe,
+ axaddr, axaddrlen,
+ axdata, axdatalen );
+ break;
+#endif
+ case IFTYPE_NULL:
+ // Efficient transmitter :-)
+ if (debug>1)
+ printf("tx null-device: %s\n", aif->callsign);
+
+ if (debug>2) {
+ printf("null_sendto() len=%d,%d ",axaddrlen,axdatalen);
+ hexdumpfp(stdout, axaddr, axaddrlen, 1);
+ printf(" // ");
+ hexdumpfp(stdout, (uint8_t*)axdata, axdatalen, 0);
+ printf("\n");
+ }
+
+ // Account the transmission anyway ;-)
+ erlang_add(aif->callsign, ERLANG_TX, axaddrlen+axdatalen + 10, 1);
+ break;
+ default:
+ break;
+ }
+}
+
+#ifndef DISABLE_IGATE
+/*
+ * Process received AX.25 packet -- for APRSIS
+ * - from AIF do find all DIGIPEATERS wanting this source.
+ * - If there are none, end processing.
+ * - Parse the received frame for possible latter filters
+ * - Feed the resulting parsed packet to each digipeater
+ *
+ * See: http://www.aprs-is.net/IGateDetails.aspx
+ *
+ * Paths
+ *
+ * IGates should use the 3rd-party format on RF of
+ * IGATECALL>APRS,GATEPATH}FROMCALL>TOCALL,TCPIP,IGATECALL*:original packet data
+ * where GATEPATH is the path that the gated packet is to follow
+ * on RF. This format will allow IGates to prevent gating the packet
+ * back to APRS-IS.
+ *
+ * q constructs should never appear on RF.
+ * The I construct should never appear on RF.
+ * Except for within gated packets, TCPIP and TCPXX should not be
+ * used on RF.
+ *
+ * Part of the Tx-IGate logic is here because we use pbuf_t data blocks:
+ *
+ * 1) The receiving station has been heard recently
+ * within defined range limits, and more recently
+ * than since given interval T1. (Range as digi-hops [N1]
+ * or coordinates, or both.)
+ *
+ * 2) The sending station has not been heard via RF
+ * within timer interval T2. (Third-party relayed
+ * frames are not analyzed for this.)
+ *
+ * 4) the receiving station has not been heard via the Internet
+ * within a predefined time period.
+ * A station is said to be heard via the Internet if packets
+ * from the station contain TCPIP* or TCPXX* in the header or
+ * if gated (3rd-party) packets are seen on RF gated by the
+ * station and containing TCPIP or TCPXX in the 3rd-party
+ * header (in other words, the station is seen on RF as being
+ * an IGate).
+ *
+ * 5) Gate all packets to RF based on criteria set by the sysop
+ * (such as callsign, object name, etc.).
+ *
+ * c) Drop everything else.
+ */
+
+static uint8_t toaprs[7] = { 'A'<<1,'P'<<1,'R'<<1,'S'<<1,' '<<1,' '<<1,0x60 };
+
+void interface_receive_3rdparty( const struct aprx_interface *aif,
+ const char *fromcall,
+ const char *origtocall,
+ const char *gwtype,
+ const char *tnc2data,
+ const int tnc2datalen )
+{
+ int d; // digipeater index
+
+ char tnc2buf1[2800];
+ uint8_t ax25buf1[2800];
+
+ time_t recent_time = tick.tv_sec - 3600; // "recent" = 1 hour
+ uint16_t filter_packettype = 0;
+ int ax25addrlen1;
+ int ax25len1;
+ int rc, tnc2addrlen1, tnc2len1;
+ uint8_t *a;
+ char *t;
+ struct pbuf_t *pb;
+
+
+ if (debug)
+ printf("interface_receive_3rdparty() aif=%p, aif->digicount=%d\n",
+ aif, aif ? aif->digisourcecount : -1);
+
+
+ if (aif == NULL) {
+ return; // Not a real interface for digi use
+ }
+
+
+ // We have to recognize incoming messages targeted to
+ // this server. For this we need to parse the TNC2 frame.
+ //
+ // We have a also filter statements to process here,
+ // we need to turn incoming APRSIS frame to something
+ // that the filter can process:
+
+ // Incoming:
+ // EI7IG-1>APRS,TCPIP*,qAC,T2IRELAND:@262231z5209.97N/00709.65W_238/019g019t049P006h95b10290.wview_5_19_0
+ // Filtered:
+ // EI7IG-1>APRS:@262231z5209.97N/00709.65W_238/019g019t049P006h95b10290.wview_5_19_0
+
+ a = ax25buf1;
+ parse_ax25addr( a, tocall, 0x60 );
+ a += 7;
+ parse_ax25addr( a, fromcall, 0x60 );
+ a += 7;
+ // No need to add generated VIA address component
+ // to this filter input data
+ a[-1] |= 0x01; // end-of-address bit
+ ax25addrlen1 = a - ax25buf1;
+ *a++ = 0x03;
+ *a++ = 0xF0;
+ if ((sizeof(ax25buf1) - tnc2datalen) <= (a-ax25buf1)) {
+ if (debug) printf(" .. data does not fit on ax25buf");
+ return;
+ }
+
+ memcpy( a, tnc2data, tnc2datalen );
+ a += tnc2datalen;
+ ax25len1 = (a - ax25buf1);
+
+ t = tnc2buf1;
+ t += sprintf(t, "%s>%s:", fromcall, origtocall);
+ tnc2addrlen1 = t - tnc2buf1 - 1;
+ if ((sizeof(tnc2buf1) - tnc2datalen) <= (t-tnc2buf1)) {
+ if (debug) printf(" .. data does not fit on tnc2buf");
+ return;
+ }
+ memcpy(t, tnc2data, tnc2datalen);
+ t += tnc2datalen;
+ tnc2len1 = (t - tnc2buf1);
+
+ // Allocate temporary pbuf for filter call use
+ pb = pbuf_new(1 /*is_aprs*/, 1 /* digi_like_aprs */,
+ tnc2addrlen1, tnc2buf1, tnc2len1,
+ ax25addrlen1, ax25buf1, ax25len1);
+ if (pb == NULL) {
+ // Urgh! Can't do a thing to this!
+ // Likely reason: ax25len+tnc2len > 2100 bytes!
+ if (debug) printf("pbuf_new() returned NULL! Discarding!\n");
+ return;
+ }
+
+ pb->source_if_group = 0; // 3rd-party frames are always from APRSIS
+
+
+ // This is APRS packet, parse for APRS meaning ...
+ rc = parse_aprs(pb, NULL); // look inside 3rd party -- historydb is looked up again below
+ if (debug) {
+ const char *srcif = aif->callsign ? aif->callsign : "??";
+ printf(".. parse_aprs() rc=%s type=0x%02x srcif=%s tnc2addr='%s' info_start='%s'\n",
+ rc ? "OK":"FAIL", pb->packettype, srcif, pb->data,
+ pb->info_start);
+ }
+
+ filter_packettype = pb->packettype;
+
+ // Check if it is a message destined to myself, and process if so.
+ rc = process_message_to_myself(aif, pb);
+
+ // Drop the temporary pbuf..
+ pbuf_put(pb);
+
+ if (rc != 0) {
+ return; // Processed as message-to-myself
+ }
+
+ if (aif->digisourcecount == 0) {
+ return; // No receivers for this source
+ }
+
+ // Feed it to digipeaters ...
+ for (d = 0; d < aif->digisourcecount; ++d) {
+ struct digipeater_source *digisrc = aif->digisources[d];
+ struct digipeater *digi = digisrc->parent;
+ struct aprx_interface *tx_aif = digi->transmitter;
+#ifndef DISABLE_IGATE
+ historydb_t *historydb = digi->historydb;
+#endif
+ char *srcif;
+ int discard_this, filter_discard;
+ char tnc2buf[2800];
+ uint8_t ax25buf[2800];
+ int ax25addrlen, ax25len;
+ int tnc2addrlen, tnc2len;
+
+ // This is APRS packet, parse for APRS meaning ...
+ rc = parse_aprs(pb,
+#ifndef DISABLE_IGATE
+ historydb // Transmitter HistoryDB
+#else
+ NULL
+#endif
+ ); // look inside 3rd party -- TODO: but what HISTORYDB ?
+ if (debug) {
+ const char *srcif = aif->callsign ? aif->callsign : "??";
+ printf(".. parse_aprs() rc=%s type=0x%02x srcif=%s tnc2addr='%s' info_start='%s'\n",
+ rc ? "OK":"FAIL", pb->packettype, srcif, pb->data,
+ pb->info_start);
+ }
+
+
+ // Produced 3rd-party packet:
+ // IGATECALL>APRS,GATEPATH:}FROMCALL>TOCALL,TCPIP,IGATECALL*:original packet data
+
+ if (debug) printf("## produce 3rd-party frame for transmit:\n");
+ // Parse the TNC2 format to AX.25 format
+ // using ax25buf[] storage area.
+ memcpy(ax25buf, toaprs, 7); // AX.25 DEST call
+
+ // FIXME: should this be IGATECALL, not tx_aif->ax25call ??
+ memcpy(ax25buf+7, tx_aif->ax25call, 7); // AX.25 SRC call
+
+ a = ax25buf + 2*7;
+
+ if ((filter_packettype & T_MESSAGE) != 0 && digisrc->msg_path != NULL) {
+ if (digisrc->msg_path != NULL) {
+ memcpy(a, digisrc->msgviapath, 7); // AX.25 VIA call for a Message
+ a += 7;
+ }
+ } else {
+ if (digisrc->via_path != NULL) {
+ memcpy(a, digisrc->ax25viapath, 7); // AX.25 VIA call
+ a += 7;
+ }
+ }
+
+ *(a-1) |= 0x01; // DEST,SRC(,VIA1) - end-of-address bit
+ ax25addrlen = a - ax25buf;
+
+ if (debug>2) {
+ printf("ax25hdr ");
+ hexdumpfp(stdout, ax25buf, ax25addrlen, 1);
+ printf("\n");
+ }
+
+ *a++ = 0x03; // UI
+ *a++ = 0xF0; // PID = 0xF0
+
+ a += sprintf((char*)a, "}%s>%s,%s,%s*:",
+ fromcall, origtocall, gwtype, tx_aif->callsign );
+ ax25len = a - ax25buf;
+ if (tnc2datalen + ax25len > sizeof(ax25buf)) {
+ // Urgh... Can not fit it in :-(
+ if(debug)printf("data does not fit into ax25buf: %d > %d\n",
+ tnc2datalen+ax25len, (int)sizeof(ax25buf));
+ continue;
+ }
+ memcpy(a, tnc2data, tnc2datalen);
+ ax25len += tnc2datalen;
+
+ // AX.25 packet is built, now build TNC2 version of it
+ t = tnc2buf;
+ t += sprintf(t, "%s>%s", tx_aif->callsign, tocall);
+ if ((filter_packettype & T_MESSAGE) != 0 && digisrc->msg_path != NULL) {
+ if (digisrc->msg_path != NULL) {
+ t += sprintf(t, ",%s", digisrc->msg_path);
+ }
+ } else {
+ if (digisrc->via_path != NULL) {
+ t += sprintf(t, ",%s", digisrc->via_path);
+ }
+ }
+ if (debug>1)printf(" tnc2addr = %s\n", tnc2buf);
+
+ tnc2addrlen = t - tnc2buf;
+ *t++ = ':';
+ t += sprintf(t, "}%s>%s,%s,%s*:",
+ fromcall, origtocall, gwtype, tx_aif->callsign );
+ if (tnc2datalen + (t-tnc2buf) +4 > sizeof(tnc2buf)) {
+ // Urgh... Can not fit it in :-(
+ if(debug)printf("data does not fit into tnc2buf: %d > %d\n",
+ (int)(tnc2datalen+(t-tnc2buf)+4),
+ (int)sizeof(tnc2buf));
+ continue;
+ }
+ memcpy(t, tnc2data, tnc2datalen);
+ t += tnc2datalen;
+ tnc2len = (t - tnc2buf);
+
+ // Allocate pbuf, it is born "gotten" (refcount == 1)
+ pb = pbuf_new(1 /*is_aprs*/, 1 /* digi_like_aprs */,
+ tnc2addrlen, tnc2buf, tnc2len,
+ ax25addrlen, ax25buf, ax25len);
+ if (pb == NULL) {
+ // Urgh! Can't do a thing to this!
+ // Likely reason: ax25len+tnc2len > 2100 bytes!
+ if (debug) printf("pbuf_new() returned NULL! Discarding!\n");
+ continue;
+ }
+
+ pb->source_if_group = 0; // 3rd-party frames are always from APRSIS
+ srcif = aif->callsign ? aif->callsign : "??";
+
+ // This is APRS packet, parse for APRS meaning ...
+ rc = parse_aprs(pb, historydb); // look inside 3rd party
+ if (debug)
+ printf(".. parse_aprs() rc=%s type=0x%02x srcif=%s tnc2addr='%s' info_start='%s'\n",
+ rc ? "OK":"FAIL", pb->packettype, srcif, pb->data,
+ pb->info_start);
+
+ // 1) - verify receiving station has been heard
+ // recently on radio
+ // 2) - sending station has not been heard recently
+ // on radio
+ // 4) - the receiving station has not been heard via
+ // the Internet within a predefined time period.
+ // (Note that _this_ packet is heard from internet,
+ // so one must not confuse this to history..
+ // Nor this siblings that are being created
+ // one for each tx-interface...)
+ //
+ // A station is said to be heard via the Internet if packets
+ // from the station contain TCPIP* or TCPXX* in the header or
+ // if gated (3rd-party) packets are seen on RF gated by the
+ // station and containing TCPIP or TCPXX in the 3rd-party
+ // header (in other words, the station is seen on RF as being
+ // an IGate).
+
+
+ // Message Tx-IGate rules..
+ discard_this = 0;
+
+ if (pb->dstname == NULL) {
+ // Sanity -- not a message..
+ discard_this = 1;
+ }
+ if (filter_packettype == 0)
+ filter_packettype = pb->packettype;
+ if ((filter_packettype & T_MESSAGE) == 0) {
+ // Not a message packet
+ discard_this = 1;
+ }
+ if ((filter_packettype & (T_NWS)) != 0) {
+ // Not a weather alert packet
+ discard_this = 1;
+ }
+
+ // Accept/Reject the packet by digipeater rx filter?
+ filter_discard = 0;
+ if (digisrc->src_filters == NULL) {
+ // No filters defined, default Tx-iGate rules apply
+ } else {
+
+ if (debug) printf("## process source filter\n");
+
+ {
+ // Stores position, and message references
+ void *v = historydb_insert_heard( historydb, pb );
+ if (debug) printf("historydb_insert_heard(APRSIS) v=%p\n", v);
+ }
+
+ filter_discard = filter_process(pb, digisrc->src_filters, historydb);
+
+ if (debug) printf("filter says: %d (%s)\n", filter_discard, (filter_discard > 0 ? "accept" : (filter_discard == 0 ? "indifferent" : "reject")));
+
+ // filter_discard > 0: accept
+ // filter_discard = 0: indifferent (not reject, not accept), tx-igate rules as is.
+ // filter_discard < 0: reject
+
+ // Manual filter says: Reject!
+ if (filter_discard < 0) {
+ if (debug) printf("REJECTED!\n");
+ discard_this = 1;
+ }
+ // Manual filter says: Accept!
+ if (discard_this && filter_discard > 0) {
+ if (debug) printf("filters say: send!\n");
+ discard_this = 0;
+ }
+ }
+
+
+ if (!discard_this && pb->dstname != NULL) {
+ // 1) - verify receiving station has been heard
+ // recently on radio
+ char recipient[10];
+ history_cell_t *hist_rx;
+ int i = 0;
+ while ( i < 9 && pb->dstname[i] != 0 && pb->dstname[i] != ' ' ) {
+ recipient[i] = pb->dstname[i];
+ ++i;
+ }
+ recipient[i] = 0;
+
+ pb->dstname_len = strlen(recipient);
+
+ // FIXME? Should test all SSIDs of this target callsign,
+ // not just this one target,
+ // if this is a T_MESSAGE! (strange BoB rules...)
+
+ hist_rx = historydb_lookup(historydb, recipient, strlen(recipient));
+ if (hist_rx == NULL) {
+ if (debug) printf("No history entry for receiving call: '%s' DISCARDING.\n", recipient);
+ discard_this = 1;
+ }
+ // See that it has 'heard on radio' flag on this tx interface
+ if (hist_rx != NULL && discard_this == 0) {
+ if (timecmp(hist_rx->last_heard[tx_aif->ifgroup], recent_time) >= 0) {
+ // Heard recently enough
+ discard_this = 0;
+ if (debug) printf("History entry for receiving call '%s' from RADIO is recent enough. KEEPING.\n", recipient);
+ }
+ }
+
+ // FIXME: Check that recipient is in our service area
+ // a) coordinate is "near by"
+ // b) last known hop-count is low enough
+ // (FIXME: RF hop-count recording infra needed!)
+
+ // 4) the receiving station has not been heard via the internet
+ if (hist_rx != NULL && timecmp(hist_rx->last_heard[0], recent_time) > 0) {
+ // "is heard recently via internet"
+ discard_this = 1;
+ if (debug) printf("History entry for sending call '%s' from APRSIS is too new. DISCARDING.\n", fromcall);
+ }
+ }
+
+ if (!discard_this) {
+ history_cell_t *hist_tx = historydb_lookup(historydb, fromcall, strlen(fromcall));
+ // If no history entry for this tx callsign,
+ // then rules 2 and 4 permit tx-igate
+ if (hist_tx != NULL) {
+ // There is a history entry for this tx callsign, check rules 2+4
+ // 2) Sending station has not been heard recently on radio (this target)
+ if (timecmp(hist_tx->last_heard[tx_aif->ifgroup], recent_time) > 0) {
+ // "is heard recently"
+ discard_this = 1;
+ if (debug) printf("History entry for sending call '%s' from RADIO is too new. DISCARDING.\n", fromcall);
+ }
+ }
+ }
+
+ {
+ // Stores position, and message references
+ void *v = historydb_insert_heard( historydb, pb );
+ if (debug) printf("historydb_insert_heard(APRSIS) v=%p\n",v);
+ }
+
+ if (filter_discard > 0 || (filter_discard == 0 && !discard_this)) {
+ // Not discarding - approved for transmission
+
+ if ((filter_packettype & T_POSITION) == 0) {
+ // TODO: For position-less packets send at first a position packet
+ // for same source call sign -- if available.
+
+ }
+
+ if (debug) printf("Send to digipeater\n");
+ digipeater_receive( digisrc, pb);
+ } else {
+ if (debug) printf("DISCARDED! (filter_discard=%d, discard_this=%d)\n",filter_discard, discard_this);
+ }
+
+ // .. and finally free up the pbuf (if refcount goes to 0)
+ pbuf_put(pb);
+ }
+}
+
+/*
+ * See if this is a message that is destined to myself
+ */
+
+#define DSTNAMELEN 16 /* 8+1+2+1 = 12, use 16 for stack align */
+
+static int dstname_is_myself(const struct pbuf_t*const pb, char *dstname, const struct aprx_interface**aifp)
+{
+ struct aprx_interface *aif;
+
+ // Copy message destination, if available.
+ *dstname = 0; // always clear first..
+ if (pb->dstname != NULL) {
+ strncpy(dstname, pb->dstname, DSTNAMELEN-1);
+ dstname[DSTNAMELEN-1] = 0;
+ }
+
+ if (strcmp(dstname, mycall) == 0) {
+ // To MYCALL account
+ return 1;
+ }
+ if (aprsis_loginid != NULL && strcmp(dstname, aprsis_loginid) == 0) {
+ // To APRSIS login account
+ return 1;
+ }
+
+ // Maybe one of my transmitters?
+ aif = find_interface_by_callsign(dstname);
+ if (aif != NULL && aif->tx_ok) {
+ // To one of my transmitter interfaces
+ *aifp = aif;
+ return 1;
+ }
+ // None of my identities
+ return 0;
+}
+
+/*
+ * Ack the message
+ */
+static void ack_message(const struct aprx_interface *const srcif, const struct aprx_interface *const aif, const struct pbuf_t*const pb, const struct aprs_message_t*const am, const char*const dstname)
+{
+ // ACK message to APRSIS is simple(ish), routing it is another thing..
+ if (srcif == &aprsis_interface) {
+ char destbuf[50];
+ int destlen = sprintf(destbuf, "%s>APRS,TCPIP*", dstname);
+ char txt[50];
+ int txtlen;
+ char *t = txt;
+ const char *s = pb->srcname;
+ int i;
+ *t++ = ':';
+ for (i = 0; i < 9 && i < pb->srcname_len; ++i) {
+ *t++ = *s++;
+ }
+ for ( ; i < 9 ; ++i) {
+ *t++ = ' ';
+ }
+ *t++ = ':';
+ *t++ = 'a';
+ *t++ = 'c';
+ *t++ = 'k';
+ for (i = 0, s = am->msgid; i < am->msgid_len; ++i) {
+ *t++ = *s++;
+ }
+ txtlen = t - txt;
+
+ aprsis_queue(destbuf, destlen,
+ qTYPE_LOCALGEN,
+ aprsis_login, txt, txtlen);
+ return;
+ }
+ // TODO: ACK things sent via radio interfaces?
+}
+
+/*
+ * A message is destined to myself, lets look closer..
+ * Return non-zero if it was recognized as targeted to this node.
+ */
+int process_message_to_myself(const struct aprx_interface*const srcif, const struct pbuf_t*const pb)
+{
+ struct aprs_message_t am;
+ int rc;
+ const struct aprx_interface*aif = srcif;
+ char dstname[DSTNAMELEN];
+
+ if ((pb->packettype & T_MESSAGE) == 0) {
+ return 0; // Not a message!
+ }
+
+ if (!dstname_is_myself(pb, dstname, &aif)) {
+ // Not destined to me
+ // This will also reject bulletins, which one is not supposed to ACK anyway..
+ return 0;
+ }
+
+ rc = parse_aprs_message(pb, &am);
+ if (rc != 0) {
+ // Not acceptable parse result
+ return 0;
+ }
+
+ // Whatever message, syslog it.
+ syslog(LOG_INFO, "%*s", pb->packet_len, pb->data);
+
+ if (am.is_rej || am.is_ack) {
+ // A REJect or ACKnowledge received, drop.
+ return 1;
+ }
+
+ // If there is msgid in the message -> I need to ACK it.
+ if (am.msgid != NULL) {
+ ack_message(srcif, aif, pb, &am, dstname);
+ }
+
+ // TODO: Process the message ?
+
+ return 1;
+}
+#endif
+
+/*
+ * Process transmit of APRS beacons
+ *
+ * Note: txbuf starts if AX.25 Control+PID bytes!
+ */
+
+int interface_transmit_beacon(const struct aprx_interface *aif, const char *src, const char *dest, const char *via, const char *txbuf, const int txlen)
+{
+ uint8_t ax25addr[90];
+ int ax25addrlen;
+ int have_fault = 0;
+ int viaindex = 1; // First via field will be index 2
+ char axaddrbuf[128];
+ char *a = axaddrbuf;
+ dupecheck_t *dupechecker;
+ int axlen;
+
+ if (debug)
+ printf("interface_transmit_beacon() aif=%p, aif->txok=%d aif->callsign='%s'\n",
+ aif, aif && aif->tx_ok ? 1 : 0, aif ? aif->callsign : "<nil>");
+
+ if (aif == NULL) return 0;
+ if (!aif->tx_ok) return 0; // Sorry, no Tx
+
+ dupechecker = digipeater_find_dupecheck(aif);
+
+ // _FOR_VALGRIND_ -- and just in case for normal use
+ memset(ax25addr, 0, sizeof(ax25addr));
+ memset(axaddrbuf, 0, sizeof(axaddrbuf));
+
+ if (parse_ax25addr(ax25addr + 7, src, 0x60)) {
+ if (debug) printf("parse_ax25addr('%s') failed. [1]\n", src);
+ return -1;
+ }
+ if (parse_ax25addr(ax25addr + 0, dest, 0x60)) {
+ if (debug) printf("parse_ax25addr('%s') failed. [2]\n", dest);
+ return -1;
+ }
+ ax25addrlen = 14; // Initial Src+Dest without any Via.
+
+ a += sprintf(axaddrbuf, "%s>%s", src, dest);
+ *a = 0;
+ axlen = a - axaddrbuf;
+
+ if (via != NULL) {
+ char viafield[12];
+ int vialen = strlen(via);
+ const char *s, *p = via;
+ const char *ve = via + vialen;
+
+ *a++ = ',';
+ axlen = a - axaddrbuf;
+ if (vialen > (sizeof(axaddrbuf)-axlen-3))
+ vialen = (sizeof(axaddrbuf)-axlen-3);
+ if (vialen > 0) {
+ memcpy(a, via, vialen);
+ a += vialen;
+ }
+ *a = 0;
+ axlen = a - axaddrbuf;
+
+ while (p < ve) {
+ int len;
+
+ for (s = p; s < ve; ++s) {
+ if (*s == ',') {
+ break;
+ }
+ }
+ // [p..s] is now one VIA field.
+ if (s == p) { // BAD!
+ have_fault = 1;
+ if (debug>1) printf(" S==P ");
+ break;
+ }
+ ++viaindex;
+ if (viaindex >= 10) {
+ if (debug) printf("too many via-fields: '%s'\n", via);
+ return -1; // Too many VIA fields
+ }
+
+ len = s - p;
+ if (len >= sizeof(viafield)) len = sizeof(viafield)-1;
+ memcpy(viafield, p, len);
+ viafield[len] = 0;
+ if (*s == ',') ++s;
+ p = s;
+ // VIA-field picked up, now parse it..
+
+ if (parse_ax25addr(ax25addr + viaindex * 7, viafield, 0x60)) {
+ // Error on VIA field value
+ if (debug) printf("parse_ax25addr('%s') failed. [3]\n", viafield);
+ return -1;
+ }
+ ax25addrlen += 7;
+ }
+ }
+
+ if (have_fault) {
+ if (debug) {
+ printf("observed a fault in inputs of interface_transmit_beacon()\n");
+ }
+ return 1;
+ }
+
+ ax25addr[ax25addrlen-1] |= 0x01; // set address field end bit
+
+
+ // Feed to dupe-filter (transmitter specific)
+ // this means we have already seen it, and when
+ // it comes back from somewhere, we do not digipeat
+ // it ourselves.
+
+ if (dupechecker != NULL)
+ dupecheck_aprs( dupechecker,
+ axaddrbuf, strlen(axaddrbuf),
+ txbuf+2, txlen-2 ); // ignore Ctrl+PID
+
+ // Transmit it to actual radio interface
+
+ interface_transmit_ax25( aif,
+ ax25addr, ax25addrlen,
+ txbuf, txlen);
+
+
+ if (rflogfile) {
+ char *axbuf;
+
+ axbuf = alloca(axlen+txlen+3);
+ memcpy( axbuf, axaddrbuf, axlen );
+ a = axbuf + axlen;
+ *a++ = ':';
+ memcpy(a, txbuf+2, txlen-2); // forget control+pid bytes..
+ a += txlen -2; // final assembled message end pointer
+
+ rflog(aif->callsign, 'T', 0, axbuf, a - axbuf);
+ }
+
+ return 0;
+}
diff --git a/keyhash.c b/keyhash.c
new file mode 100644
index 0000000..8230d1a
--- /dev/null
+++ b/keyhash.c
@@ -0,0 +1,91 @@
+/********************************************************************
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ ********************************************************************/
+
+/*
+ * Keyhash routines for the system.
+ *
+ * What is needed is _fast_ hash function. Preferrably arithmethic one,
+ * which does not need table lookups, and can work with aligned 32 bit
+ * data -- but also on unaligned, and on any byte counts...
+ *
+ * Contenders:
+ * http://burtleburtle.net/bob/c/lookup3.c
+ * http://www.ibiblio.org/pub/Linux/devel/lang/c/mph-1.2.tar.gz
+ * http://www.concentric.net/~Ttwang/tech/inthash.htm
+ * http://isthe.com/chongo/tech/comp/fnv/
+ *
+ * Currently using FNV-1a
+ *
+ */
+
+/*
+// FNV-1a hash from http://isthe.com/chongo/tech/comp/fnv/
+//
+// It is algorithmic hash without memory lookups.
+// Compiler seems to prefer actual multiplication over a bunch of
+// fixed shifts and additions.
+*/
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "keyhash.h"
+
+void keyhash_init(void) { }
+
+uint32_t __attribute__((pure)) keyhash(const void const *p, int len, uint32_t hash)
+{
+ const uint8_t *u = p;
+ int i;
+#define FNV_32_PRIME 16777619U
+#define FNV_32_OFFSET 2166136261U
+
+ if (hash == 0)
+ hash = (uint32_t)FNV_32_OFFSET;
+
+ for (i = 0; i < len; ++i, ++u) {
+#if defined(NO_FNV_GCC_OPTIMIZATION)
+ hash *= FNV_32_PRIME;
+#else
+ hash += (hash<<1) + (hash<<4) + (hash<<7) +
+ (hash<<8) + (hash<<24);
+#endif
+ hash ^= (uint32_t) *u;
+ }
+ return hash;
+}
+
+/* The data material is known to contain ASCII, and if any value in there
+ * is a lower case letter, it is first converted to upper case one.
+*/
+uint32_t __attribute__((pure)) keyhashuc(const void const *p, int len, uint32_t hash)
+{
+ const uint8_t *u = p;
+ int i;
+
+ if (hash == 0)
+ hash = (uint32_t)FNV_32_OFFSET;
+
+ for (i = 0; i < len; ++i, ++u) {
+#if defined(NO_FNV_GCC_OPTIMIZATION)
+ hash *= FNV_32_PRIME;
+#else
+ hash += (hash<<1) + (hash<<4) + (hash<<7) +
+ (hash<<8) + (hash<<24);
+#endif
+ uint32_t c = *u;
+ // Is it lower case ASCII letter ?
+ if ('a' <= c && c <= 'z') {
+ // convert to upper case.
+ c -= ('a' - 'A');
+ }
+ hash ^= c;
+ }
+ return hash;
+}
diff --git a/keyhash.h b/keyhash.h
new file mode 100644
index 0000000..d8f6377
--- /dev/null
+++ b/keyhash.h
@@ -0,0 +1,17 @@
+/********************************************************************
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ ********************************************************************/
+
+#ifndef KEYHASH_H
+#define KEYHASH_H
+
+extern void keyhash_init(void);
+extern unsigned int keyhash(const void *s, int slen, unsigned int hash0);
+extern unsigned int keyhashuc(const void *s, int slen, unsigned int hash0);
+
+#endif
diff --git a/kiss.c b/kiss.c
new file mode 100644
index 0000000..b6c5e7b
--- /dev/null
+++ b/kiss.c
@@ -0,0 +1,710 @@
+/* **************************************************************** *
+ * *
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ * **************************************************************** */
+
+#include "aprx.h"
+
+
+/*
+ * kissprocess() -- the S->rdline[] array has a KISS frame after
+ * KISS escape decode. The frame begins with KISS command byte, then
+ * AX25 headers and payload, and possibly a CRC-checksum.
+ * Frame length is in S->rdlinelen variable.
+ */
+
+/* KA9Q describes the KISS frame format as follows:
+
+ http://www.ka9q.net/papers/kiss.html
+
+ - - - - - - - - -
+ 4. Control of the KISS TNC
+
+ To distinguish between command and data frames on the host/TNC link,
+ the first byte of each asynchronous frame between host and TNC is
+ a "type" indicator.
+
+ This type indicator byte is broken into two 4-bit nibbles so that
+ the low-order nibble indicates the command number (given in the table
+ below) and the high-order nibble indicates the port number for that
+ particular command. In systems with only one HDLC port, it is by definition
+ Port 0. In multi-port TNCs, the upper 4 bits of the type indicator
+ byte can specify one of up to sixteen ports. The following commands
+ are defined in frames to the TNC (the "Command" field is in hexadecimal):
+
+ . . . . . .
+
+ CMD code 0 is for the data frame, and is only one present coming from
+ TNC to host.
+
+ - - - - - - - - -
+
+ SYMEK et al. have defined a way to run CRC inside KISS frames to
+ verify that the KISS-frame itself is correct:
+
+ http://www.symek.com/g/smack.html
+ http://www.ir3ip.net/iw3fqg/doc/smak.htm
+
+ SMACK variation recycles the top-most bit of the TNC-id nibble, and
+ thus permits up to 8 TNC ports on line. Top-most bit is always one
+ on SMACK frames.
+
+ SMACK runs CRC16 over whole KISS frame buffer, including the CMD byte.
+ The CRC-code is thus _different_ from what will be sent out on radio,
+ the latter being CRC-CCITT (see further below):
+
+ Following CRC16-polynome is used:
+
+ X^16 + X^15 + X^2 + 1
+
+ The CRC-generator is preset to zero.
+
+ Chosen initialize to zero does mean that after a correct packet with a
+ correct checksum is ran thru this CRC, the output checksum will be zero.
+
+
+ - - - - - - - - -
+
+ Where is FLEXNET specification?
+
+
+*/
+
+
+
+/*
+ * kissencoder(): If (cmdbyte & 0x80) is set, then this
+ * produces SMACK format frame, otherwise
+ * plain KISS.
+ *
+ */
+
+int kissencoder( void *kissbuf, int kissspace, LineType linetype,
+ const void *pktbuf, int pktlen, int cmdbyte )
+{
+ uint8_t *kb = kissbuf;
+ uint8_t *ke = kb + kissspace - 3;
+ const uint8_t *pkt = pktbuf;
+ int i;
+ uint16_t crc16;
+ uint16_t crcflex;
+
+ crc16 = crc16_table[cmdbyte & 0xFF];
+ crcflex = 0xff00 ^ crc_flex_table[(~cmdbyte) & 0xff];
+
+ /* Expect the KISS buffer to be at least ... 8 bytes.. */
+
+ *kb++ = KISS_FEND;
+ *kb++ = cmdbyte;
+
+ for (i = 0; i < pktlen && kb < ke; ++i, ++pkt) {
+ // Calc CRCs while encoding data..
+ int b = *pkt;
+ crc16 = ((crc16 >> 8) & 0xff) ^ crc16_table[(crc16 ^ b) & 0xFF];
+ crcflex = (crcflex << 8) ^ crc_flex_table[((crcflex >> 8) ^ b) & 0xff];
+
+ if (b == KISS_FEND) {
+ *kb++ = KISS_FESC;
+ *kb++ = KISS_TFEND;
+ } else {
+ *kb++ = b;
+ if (b == KISS_FESC)
+ *kb++ = KISS_TFESC;
+ }
+ }
+ /* If caller is asking for SMACK format frame, then
+ store calculated CRC on frame. - CRC-bytes must be KISS escaped! */
+ /* If caller is asking for SMACK/FLEXNET format frame, then
+ store calculated CRC on frame. - CRC-bytes must be KISS escaped! */
+ if (linetype == LINETYPE_KISSSMACK ||
+ linetype == LINETYPE_KISSFLEXNET) {
+ int crc, b;
+ if (linetype == LINETYPE_KISSSMACK) {
+ crc = crc16;
+ } else if (linetype == LINETYPE_KISSFLEXNET) {
+ crc = crcflex;
+ } else {
+ // Silence compiler warning, this branch is never reached..
+ crc = 0;
+ }
+
+ b = crc & 0xFF; /* low crc byte */
+ if (b == KISS_FEND) {
+ if (kb < ke)
+ *kb++ = KISS_FESC;
+ if (kb < ke)
+ *kb++ = KISS_TFEND;
+ } else {
+ if (kb < ke)
+ *kb++ = b;
+ if (b == KISS_FESC && kb < ke)
+ *kb++ = KISS_TFESC;
+ }
+ b = (crc >> 8) & 0xFF; /* high crc byte */
+ if (b == KISS_FEND) {
+ if (kb < ke)
+ *kb++ = KISS_FESC;
+ if (kb < ke)
+ *kb++ = KISS_TFEND;
+ } else {
+ if (kb < ke)
+ *kb++ = b;
+ if (b == KISS_FESC && kb < ke)
+ *kb++ = KISS_TFESC;
+ }
+ }
+ if (kb < ke) {
+ *kb++ = KISS_FEND;
+ return (kb - (uint8_t *) (kissbuf));
+ } else {
+ /* Didn't fit in... */
+ return 0;
+ }
+}
+
+
+static int kissprocess(struct serialport *S)
+{
+ int i;
+ int cmdbyte = S->rdline[0];
+ int tncid = (cmdbyte >> 4) & 0x0F;
+
+ /* --
+ * C0 00
+ * 82 A0 B4 9A 88 A4 60
+ * 9E 90 64 90 A0 9C 72
+ * 9E 90 64 A4 88 A6 E0
+ * A4 8C 9E 9C 98 B2 61
+ * 03 F0
+ * 21 36 30 32 39 2E 35 30 4E 2F 30 32 35 30 35 2E 34 33 45 3E 20 47 43 53 2D 38 30 31 20
+ * C0
+ * --
+ */
+
+ /* printf("kissprocess() cmdbyte=%02X len=%d ",cmdbyte,S->rdlinelen); */
+
+ /* Ok, cmdbyte tells us something, and we should ignore the
+ frame if we don't know it... */
+
+ if ((cmdbyte & 0x0F) != 0) {
+ /* There should NEVER be any other value in the CMD bits
+ than 0 coming from TNC to host! */
+ /* printf(" ..bad CMD byte\n"); */
+ if (debug) {
+ printf("%ld\tTTY %s: Bad CMD byte on KISS frame: ", tick.tv_sec, S->ttyname);
+ hexdumpfp(stdout, S->rdline, S->rdlinelen, 1);
+ printf("\n");
+ }
+ erlang_add(S->ttycallsign[tncid], ERLANG_DROP, S->rdlinelen, 1); /* Account one packet */
+ return -1;
+ }
+
+ if (S->linetype == LINETYPE_KISS && (cmdbyte & 0x20)) {
+ // Huh? Perhaps a FLEXNET packet?
+ int crcflex = calc_crc_flex(S->rdline, S->rdlinelen);
+ if (crcflex == 0x7070) {
+ if (debug) printf("ALERT: Looks like received KISS frame is a FLEXNET with CRC!\n");
+ S->linetype = LINETYPE_KISSFLEXNET;
+ }
+ }
+ if (S->linetype == LINETYPE_KISS && (cmdbyte & 0x80)) {
+ // Huh? Perhaps a SMACK packet?
+ int smack_ok = check_crc_16(S->rdline, S->rdlinelen);
+ if (smack_ok == 0) {
+ if (debug) printf("ALERT: Looks like received KISS frame is a SMACK with CRC!\n");
+ S->linetype = LINETYPE_KISSSMACK;
+ }
+ }
+
+ /* Are we expecting FLEXNET KISS ? */
+ if (S->linetype == LINETYPE_KISSFLEXNET && (cmdbyte & 0x20)) {
+ int crc;
+ tncid &= ~0x20; // FlexNet puts 0x20 as indication of CRC presence..
+
+ if (S->ttycallsign[tncid] == NULL) {
+ /* D'OH! received packet on multiplexer tncid without
+ callsign definition! We discard this packet! */
+ if (debug > 0) {
+ printf("%ld\tTTY %s: Bad TNCID on CMD byte on a KISS frame: %02x No interface configured for it! ", tick.tv_sec, S->ttyname, cmdbyte);
+ hexdumpfp(stdout, S->rdline, S->rdlinelen, 1);
+ printf("\n");
+ }
+ erlang_add(S->ttycallsign[tncid], ERLANG_DROP, S->rdlinelen, 1); /* Account one packet */
+ return -1;
+ }
+ crc = calc_crc_flex(S->rdline, S->rdlinelen);
+ if (crc != 0x7070) {
+ if (debug) {
+ printf("%ld\tTTY %s tncid %d: Received FLEXNET frame with invalid CRC %04x: ",
+ tick.tv_sec, S->ttyname, tncid, crc);
+ hexdumpfp(stdout, S->rdline, S->rdlinelen, 1);
+ printf("\n");
+ }
+ erlang_add(S->ttycallsign[tncid], ERLANG_DROP, S->rdlinelen, 1); // Account one packet
+ return -1; // The CRC was invalid..
+ }
+ S->rdlinelen -= 2; // remove 2 bytes!
+ }
+
+ /* Are we excepting BPQ "CRC" (XOR-sum of data) */
+ if (S->linetype == LINETYPE_KISSBPQCRC) {
+ /* TODO: in what conditions the "CRC" is calculated and when not ? */
+ int xorsum = 0;
+
+ if (S->ttycallsign[tncid] == NULL) {
+ /* D'OH! received packet on multiplexer tncid without
+ callsign definition! We discard this packet! */
+ if (debug > 0) {
+ printf("%ld\tTTY %s: Bad TNCID on CMD byte on a KISS frame: %02x No interface configured for it! ", tick.tv_sec, S->ttyname, cmdbyte);
+ hexdumpfp(stdout, S->rdline, S->rdlinelen, 1);
+ printf("\n");
+ }
+ erlang_add(S->ttycallsign[tncid], ERLANG_DROP, S->rdlinelen, 1); /* Account one packet */
+ return -1;
+ }
+
+ for (i = 1; i < S->rdlinelen; ++i)
+ xorsum ^= S->rdline[i];
+ xorsum &= 0xFF;
+ if (xorsum != 0) {
+ if (debug) {
+ printf("%ld\tTTY %s tncid %d: Received bad BPQCRC: %02x: ", tick.tv_sec, S->ttyname, tncid, xorsum);
+ hexdumpfp(stdout, S->rdline, S->rdlinelen, 1);
+ printf("\n");
+ }
+ erlang_add(S->ttycallsign[tncid], ERLANG_DROP, S->rdlinelen, 1); /* Account one packet */
+ return -1;
+ }
+ S->rdlinelen -= 1; /* remove the sum-byte from tail */
+ if (debug > 2)
+ printf("%ld\tTTY %s tncid %d: Received OK BPQCRC frame\n", tick.tv_sec, S->ttyname, tncid);
+ }
+ /* Are we expecting SMACK ? */
+ if (S->linetype == LINETYPE_KISSSMACK) {
+
+ tncid &= 0x07; /* Chop off top bit */
+
+ if (S->ttycallsign[tncid] == NULL) {
+ /* D'OH! received packet on multiplexer tncid without
+ callsign definition! We discard this packet! */
+ if (debug > 0) {
+ printf("%ld\tTTY %s: Bad TNCID on CMD byte on a KISS frame: %02x No interface configured for it! ", tick.tv_sec, S->ttyname, cmdbyte);
+ hexdumpfp(stdout, S->rdline, S->rdlinelen, 1);
+ printf("\n");
+ }
+ erlang_add(S->ttycallsign[tncid], ERLANG_DROP, S->rdlinelen, 1); /* Account one packet */
+ return -1;
+ }
+
+ if ((cmdbyte & 0x8F) == 0x80) {
+ /* SMACK data frame */
+
+ if (debug > 3)
+ printf("%ld\tTTY %s tncid %d: Received SMACK frame\n", tick.tv_sec, S->ttyname, tncid);
+
+ if (!(S->smack_subids & (1 << tncid))) {
+ if (debug)
+ printf("%ld\t... marking received SMACK\n", tick.tv_sec);
+ }
+ S->smack_subids |= (1 << tncid);
+
+ /* It is SMACK frame -- KISS with CRC16 at the tail.
+ Now we ignore the TNC-id number field.
+ Verify the CRC.. */
+
+ // Whole buffer including CMD-byte!
+ if (check_crc_16(S->rdline, S->rdlinelen) != 0) {
+ if (debug) {
+ printf("%ld\tTTY %s tncid %d: Received SMACK frame with invalid CRC: ",
+ tick.tv_sec, S->ttyname, tncid);
+ hexdumpfp(stdout, S->rdline, S->rdlinelen, 1);
+ printf("\n");
+ }
+ erlang_add(S->ttycallsign[tncid], ERLANG_DROP, S->rdlinelen, 1); // Account one packet
+ return -1; /* The CRC was invalid.. */
+ }
+
+ S->rdlinelen -= 2; /* Chop off the two CRC bytes */
+
+ } else if ((cmdbyte & 0x8F) == 0x00) {
+ /*
+ * Expecting SMACK data, but got plain KISS data.
+ * Send a flow-rate limited probes to TNC to enable
+ * SMACK -- lets use 30 minutes window...
+ */
+
+
+ S->smack_subids &= ~(1 << tncid); // Turn off the SMACK mode indication bit..
+
+ if (debug > 2)
+ printf("%ld\tTTY %s tncid %d: Expected SMACK, got KISS.\n", tick.tv_sec, S->ttyname, tncid);
+
+ if (timecmp(S->smack_probe[tncid], tick.tv_sec) < 0) {
+ uint8_t probe[4];
+ uint8_t kissbuf[12];
+ int kisslen;
+
+ probe[0] = cmdbyte | 0x80; /* Make it into SMACK */
+ probe[1] = 0;
+
+ /* Convert the probe packet to KISS frame */
+ kisslen = kissencoder( kissbuf, sizeof(kissbuf), S->linetype,
+ &(probe[1]), 1, probe[0] );
+
+ /* Send probe message.. */
+ if (S->wrlen + kisslen < sizeof(S->wrbuf)) {
+ /* There is enough space in writebuf! */
+
+ memcpy(S->wrbuf + S->wrlen, kissbuf, kisslen);
+ S->wrlen += kisslen;
+ /* Flush it out.. and if not successfull,
+ poll(2) will take care of it soon enough.. */
+ ttyreader_linewrite(S);
+
+ S->smack_probe[tncid] = tick.tv_sec + 1800; /* 30 minutes */
+
+ if (debug)
+ printf("%ld\tTTY %s tncid %d: Sending SMACK activation probe packet\n", tick.tv_sec, S->ttyname, tncid);
+
+ }
+ /* Else no space to write ? Huh... */
+ }
+ } else {
+ // Else... there should be no other kind data frames
+ if (debug) {
+ printf("%ld\tTTY %s: Bad CMD byte on expected SMACK frame: %02x, len=%d: ",
+ tick.tv_sec, S->ttyname, cmdbyte, S->rdlinelen);
+ hexdumpfp(stdout, S->rdline, S->rdlinelen, 1);
+ printf("\n");
+ }
+ erlang_add(S->ttycallsign[tncid], ERLANG_DROP, S->rdlinelen, 1); /* Account one packet */
+ return -1;
+ }
+ }
+
+ /* Are we expecting Basic KISS ? */
+ if (S->linetype == LINETYPE_KISS) {
+ if (S->ttycallsign[tncid] == NULL) {
+ /* D'OH! received packet on multiplexer tncid without
+ callsign definition! We discard this packet! */
+ if (debug > 0) {
+ printf("%ld\tTTY %s: Bad TNCID on CMD byte on a KISS frame: %02x No interface configured for it! ", tick.tv_sec, S->ttyname, cmdbyte);
+ hexdumpfp(stdout, S->rdline, S->rdlinelen, 1);
+ printf("\n");
+ }
+ erlang_add(S->ttycallsign[tncid], ERLANG_DROP, S->rdlinelen, 1); /* Account one packet */
+ return -1;
+ }
+ }
+
+
+ if (S->rdlinelen < 17) {
+ /* 7+7+2 bytes of minimal AX.25 frame + 1 for KISS CMD byte */
+
+ /* Too short frame.. */
+ /* printf(" ..too short a frame for anything\n"); */
+ erlang_add(S->ttycallsign[tncid], ERLANG_DROP, S->rdlinelen, 1); /* Account one packet */
+ return -1;
+ }
+
+ /* Valid AX.25 HDLC frame byte sequence is now at
+ S->rdline[1..S->rdlinelen-1]
+ */
+
+ /* Send the frame to APRS-IS, return 1 if valid AX.25 UI message, does not
+ validate against valid APRS message rules... (TODO: it could do that too) */
+
+ // The AX25_TO_TNC2 does validate the AX.25 packet,
+ // converts it to "TNC2 monitor format" and sends it to
+ // Rx-IGate functionality. Returns non-zero only when
+ // AX.25 header is OK, and packet is sane.
+
+ erlang_add(S->ttycallsign[tncid], ERLANG_RX, S->rdlinelen, 1); /* Account one packet */
+
+ if (ax25_to_tnc2(S->interface[tncid], S->ttycallsign[tncid], tncid,
+ cmdbyte, S->rdline + 1, S->rdlinelen - 1)) {
+ // The packet is valid per AX.25 header bit rules.
+
+#ifdef PF_AX25 /* PF_AX25 exists -- highly likely a Linux system ! */
+ /* Send the frame without cmdbyte to internal AX.25 network */
+ if (S->netax25[tncid] != NULL)
+ netax25_sendax25(S->netax25[tncid], S->rdline + 1, S->rdlinelen - 1);
+#endif
+
+ } else {
+ // The packet is not valid per AX.25 header bit rules
+ erlang_add(S->ttycallsign[tncid], ERLANG_DROP, S->rdlinelen, 1); /* Account one packet */
+
+ if (aprxlogfile) {
+ // NOT replaced with aprxlog() -- because this is a bit more complicated..
+ FILE *fp = fopen(aprxlogfile, "a");
+ if (fp) {
+ char timebuf[60];
+ printtime(timebuf, sizeof(timebuf));
+ setlinebuf(fp);
+
+ fprintf(fp, "%s ax25_to_tnc2(%s,len=%d) rejected the message: ", timebuf, S->ttycallsign[tncid], S->rdlinelen-1);
+ hexdumpfp(fp, S->rdline, S->rdlinelen, 1);
+ fprintf(fp, "\n");
+ fclose(fp);
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+/*
+ * ttyreader_pullkiss() -- pull KISS (or KISS+CRC) frame, and call KISS processor
+ */
+
+int kiss_pullkiss(struct serialport *S)
+{
+ /* printf("ttyreader_pullkiss() rdlen=%d rdcursor=%d, state=%d\n",
+ S->rdlen, S->rdcursor, S->kissstate); fflush(stdout); */
+
+ /* At incoming call there is at least one byte in between
+ S->rdcursor and S->rdlen */
+
+ /* Phases:
+ kissstate == 0: hunt for KISS_FEND, discard everything before it.
+ kissstate != 0: reading has globbed up preceding KISS_FENDs
+ ("HDLC flags") and the cursor is in front of a frame
+ */
+
+ /* There are TNCs that use "shared flags" - only one FEND in between
+ data frames. */
+
+ if (S->kissstate == KISSSTATE_SYNCHUNT) {
+ /* Hunt for KISS_FEND, discard everything until then! */
+ int c;
+ for (;;) {
+ c = ttyreader_getc(S);
+ if (c < 0)
+ return c; /* Out of buffer, stay in state,
+ return latter when there is some
+ refill */
+ if (c == KISS_FEND) /* Found the sync-byte ! change state! */
+ break;
+ }
+ S->kissstate = KISSSTATE_COLLECTING;
+ }
+
+
+ if (S->kissstate != KISSSTATE_SYNCHUNT) {
+ /* Normal processing mode */
+
+ int c;
+
+ for (;;) {
+ c = ttyreader_getc(S);
+ if (c < 0)
+ return c; /* Out of input stream, exit now,
+ come back latter.. */
+
+ /* printf(" %02X", c);
+ if (c == KISS_FEND) { printf("\n");fflush(stdout); } */
+
+ if (c == KISS_FEND) {
+ /* Found end-of-frame character -- or possibly beginning..
+ This never exists in datastream except as itself. */
+
+ if (S->rdlinelen > 0) {
+ /* Non-zero sized frame Process it away ! */
+ kissprocess(S);
+ S->kissstate =
+ KISSSTATE_COLLECTING;
+ S->rdlinelen = 0;
+ }
+
+ /* rdlinelen == 0 because we are receiving consequtive
+ FENDs, or just processed our previous frame. Treat
+ them the same: discard this byte. */
+
+ continue;
+ }
+
+ if (S->kissstate == KISSSTATE_KISSFESC) {
+
+ /* We have some char, state switches to normal collecting */
+ S->kissstate = KISSSTATE_COLLECTING;
+
+ if (c == KISS_TFEND)
+ c = KISS_FEND;
+ else if (c == KISS_TFESC)
+ c = KISS_FESC;
+ else
+ continue; /* Accepted chars after KISS_FESC
+ are only TFEND and TFESC.
+ Others must be discarded. */
+
+ } else { /* Normal collection mode */
+
+ if (c == KISS_FESC) {
+ S->kissstate = KISSSTATE_KISSFESC;
+ continue; /* Back to top of the loop and continue.. */
+ }
+
+ }
+
+
+ if (S->rdlinelen >= (sizeof(S->rdline) - 3)) {
+ /* Too long ! Way too long ! */
+
+ S->kissstate = KISSSTATE_SYNCHUNT; /* Sigh.. discard it. */
+ S->rdlinelen = 0;
+ if (debug) {
+ printf("%ld\tTTY %s: Too long frame to be KISS: ", tick.tv_sec, S->ttyname);
+ hexdumpfp(stdout, S->rdline, S->rdlinelen, 1);
+ printf("\n");
+ }
+ continue;
+ }
+
+ /* Put it on record store: */
+ S->rdline[S->rdlinelen++] = c;
+ } /* .. for(..) loop of data collecting */
+
+ }
+ /* .. normal consumption mode ... */
+ return 0;
+}
+
+
+/*
+ * kiss_kisswrite() -- write out buffered data
+ */
+void kiss_kisswrite(struct serialport *S, const int tncid, const uint8_t *ax25raw, const int ax25rawlen)
+{
+ int i, len, ssid;
+ uint8_t kissbuf[2300];
+
+ if (debug) {
+ printf("kiss_kisswrite(->%s, axlen=%d)", S->ttycallsign[tncid], ax25rawlen);
+ }
+ if (S->fd < 0) {
+ if (debug)
+ printf("NOTE: Write to non-open serial port discarded.");
+ return;
+ }
+
+
+ if ((S->linetype != LINETYPE_KISS) && (S->linetype != LINETYPE_KISSSMACK) &&
+ (S->linetype != LINETYPE_KISSFLEXNET) && (S->linetype != LINETYPE_KISSBPQCRC)) {
+ if (debug)
+ printf("WARNING: WRITING KISS FRAMES ON SERIAL/TCP LINE OF NO KISS TYPE IS UNSUPPORTED!\n");
+ return;
+ }
+
+
+ if ((S->wrlen == 0) || (S->wrlen > 0 && S->wrcursor >= S->wrlen)) {
+ S->wrlen = S->wrcursor = 0;
+ } else {
+ /* There is some data in between wrcursor and wrlen */
+ len = S->wrlen - S->wrcursor;
+ if (len > 0) {
+ i = write(S->fd, S->wrbuf + S->wrcursor, len);
+ } else
+ i = 0;
+ if (i > 0) { /* wrote something */
+ S->wrcursor += i;
+ len = S->wrlen - S->wrcursor;
+ if (len == 0) {
+ S->wrcursor = S->wrlen = 0; /* wrote all ! */
+ } else {
+ /* compact the buffer a bit */
+ memcpy(S->wrbuf, S->wrbuf + S->wrcursor, len);
+ S->wrcursor = 0;
+ S->wrlen = len;
+ }
+ }
+ }
+
+ ssid = (tncid << 4) | ((S->linetype == LINETYPE_KISSSMACK) ? 0x80 : 0x00);
+ if (S->linetype == LINETYPE_KISSFLEXNET) ssid |= 0x20; // CRC presence
+
+ len = kissencoder( kissbuf, sizeof(kissbuf), S->linetype, ax25raw, ax25rawlen, ssid );
+
+ if (debug>2) {
+ printf("kiss-encoded: ");
+ hexdumpfp(stdout, kissbuf, len, 1);
+ printf("\n");
+ }
+
+ // Will the KISS encoded frame fit in the link buffer?
+ if ((S->wrlen + len) < sizeof(S->wrbuf)) {
+ memcpy(S->wrbuf + S->wrlen, kissbuf, len);
+ S->wrlen += len;
+ erlang_add(S->ttycallsign[tncid], ERLANG_TX, ax25rawlen, 1);
+
+ if (debug)
+ printf(" .. put %d bytes of KISS frame on IO buffer\n",len);
+ } else {
+ // No fit!
+ if (debug)
+ printf(" .. %d bytes of KISS frame did not fit on IO buffer\n",len);
+ return;
+ }
+
+ // Try to write it immediately
+ len = S->wrlen - S->wrcursor;
+ if (len > 0)
+ i = write(S->fd, S->wrbuf + S->wrcursor, len);
+ else
+ i = 0;
+ if (i > 0) { /* wrote something */
+ S->wrcursor += i;
+ len = S->wrlen - S->wrcursor; /* all done? */
+ if (len == 0) {
+ S->wrcursor = S->wrlen = 0; /* wrote all ! */
+ } else {
+ /* compact the buffer a bit */
+ memcpy(S->wrbuf, S->wrbuf + S->wrcursor, len);
+ S->wrcursor = 0;
+ S->wrlen = len;
+ }
+ }
+}
+
+
+void kiss_poll(struct serialport *S)
+{
+ uint8_t probe[1];
+ uint8_t kissbuf[12];
+ int kisslen;
+ int tncid;
+
+ for (tncid = 0; tncid < 16; ++tncid) {
+
+ if (S->interface[tncid] == NULL) {
+ // No sub-interface here..
+ continue;
+ }
+
+ probe[0] = 0x0E | (tncid << 4);
+
+ /* Convert the probe packet to KISS frame */
+ kisslen = kissencoder( kissbuf, sizeof(kissbuf), S->linetype,
+ &(probe[0]), 0, probe[0] );
+
+ /* Send probe message.. */
+ if (S->wrlen + kisslen < sizeof(S->wrbuf)) {
+ /* There is enough space in writebuf! */
+
+ memcpy(S->wrbuf + S->wrlen, kissbuf, kisslen);
+ S->wrlen += kisslen;
+ /* Flush it out.. and if not successfull,
+ poll(2) will take care of it soon enough.. */
+ ttyreader_linewrite(S);
+
+ if (debug)
+ printf("%ld.%06d\tTTY %s tncid %d: Sending KISS POLL\n", (long)tick.tv_sec, (int)tick.tv_usec, S->ttyname, tncid);
+ }
+ }
+}
diff --git a/logrotate.aprx.in b/logrotate.aprx.in
new file mode 100644
index 0000000..212152d
--- /dev/null
+++ b/logrotate.aprx.in
@@ -0,0 +1,8 @@
+ at VARLOG@/aprx-rf.log @VARLOG@/aprx.log @VARLOG@/dprs.log @VARLOG@/erlang.log {
+ weekly
+ rotate 4
+ compress
+ missingok
+ notifempty
+ create 644 root adm
+}
diff --git a/man-to-html.sh b/man-to-html.sh
new file mode 100644
index 0000000..a9903fa
--- /dev/null
+++ b/man-to-html.sh
@@ -0,0 +1,118 @@
+#! /bin/sh
+
+## man-page to HTML format converter, when existing ones
+## were seriously unacceptable form...
+##
+## By Matti Aarnio, OH2MQK, <oh2mqk at sral.fi>, about 1995
+
+unset LC_CTYPE
+
+LANG=en_US
+export LANG
+
+TERM=xterm
+export TERM
+
+COLUMNS=80
+export COLUMNS
+
+LINES=9999
+export LINES
+
+
+echo "<HTML><HEAD><TITLE>"
+basename "$1"
+echo '</TITLE></HEAD><BODY BGCOLOR=white><PRE>'
+groff -t -man -Tascii -P-c "$1" | \
+ perl -e '
+ #select STDIN; $| = 1;
+ select STDERR; $| = 1;
+ #select STDOUT; $| = 1;
+ $h = "\010";
+ $c0 = undef;
+ while(read(STDIN,$c,1) > 0) {
+
+#printf STDERR "c0 = \"%s\" c = \"%s\"\n",
+# !defined $c0 ? "<UNDEF>" : (ord($c0) < 32 ?
+# sprintf("\\%03o",ord($c0)): $c0),
+# ord($c) < 32 ? sprintf("\\%03o",ord($c)) : $c;
+
+ if (defined $c0) {
+ if ($c eq $h) {
+ # X ^H * -> bold/italic/something
+ read(STDIN,$c1,1);
+
+#printf STDERR " .. c1 = \"%s\"\n",
+# ord($c1) < 32 ? sprintf("\\%03o",ord($c1)) : $c1;
+
+ if ($c0 eq $c1) {
+ # bold
+ if ($c0 eq "&") { $c0 = "&"; }
+ elsif ($c0 eq "<") { $c0 = "<"; }
+ elsif ($c0 eq ">") { $c0 = ">"; }
+ printf STDOUT "<B>%s</B>",$c0;
+ } elsif ($c0 eq "_") {
+ # italic
+ if ($c1 eq "&") { $c1 = "&"; }
+ elsif ($c1 eq "<") { $c1 = "<"; }
+ elsif ($c1 eq ">") { $c1 = ">"; }
+ printf STDOUT "<I>%s</I>",$c1;
+ } elsif ($c0.$c1 eq "+o") {
+ # Bullet
+ printf STDOUT "<B>•</B>";
+ } else {
+ # something -- overstrike ?
+ if ($c1 eq "&") { $c1 = "&"; }
+ elsif ($c1 eq "<") { $c1 = "<"; }
+ elsif ($c1 eq ">") { $c1 = ">"; }
+ printf STDOUT "<B>%s</B>",$c1;
+ }
+ $c0 = undef;
+ if ($c1 eq "\n") { printf STDOUT "\n"; }
+ } else {
+ # Not X ^H *, but X is defined.
+ if ($c0 eq "&") { $c0 = "&"; }
+ elsif ($c0 eq "<") { $c0 = "<"; }
+ elsif ($c0 eq ">") { $c0 = ">"; }
+ printf STDOUT "%s",$c0;
+ $c0 = $c;
+ }
+ } else {
+ # $c0 not defined!
+ $c0 = $c;
+ }
+ } # ... while()
+ if ($c0) { printf STDOUT "%s",$c0; }' | \
+ perl -ne '
+ s{</B>(\s*)<B>}{\1}og;
+ s{</I>(\s*)<I>}{\1}og;
+ s{</U>(\s*)<U>}{\1}og;
+ s{</I><B>_</B><I>}{_}og;
+ # Ordinary man-pages
+ s{<I>([-.0-9a-zA-Z_]+)</I>\((\dzm)\)}{<A HREF="\1.\2.html"><I>\1</I>(\2)</A>}og;
+
+ # Ordinary PERL PODs
+ s{<I>([-.0-9a-zA-Z_]+::[-.0-9a-zA-Z_]+)</I>\((\d\w+)\)}{<A HREF="\1.\2.html"><I>\1</I>(\2)</A>}og;
+ print;' | \
+ perl -e '
+ @labels=();
+ while (<STDIN>) {
+ if (m{^<B>(.*)</B>$}o) {
+ my $n = $1; $n =~ s/ /_/g;
+ printf "<A NAME=\"%s\"></A>",$n;
+ push @labels, $n;
+ }
+ if (m{^ <B>(.*)</B>$}o) {
+ my $n = $1; $n =~ s/ /_/g;
+ printf "<A NAME=\"%s\"></A>",$n;
+ push @labels, $n;
+ }
+ print;
+ }
+ printf "<p><p>\n<ul></n";
+ foreach $n (@labels) {
+ printf "<li> <A HREF=\"#%s\">%s</A>\n",$n,$n;
+ }
+ printf "</ul>\n";
+ '
+echo "</PRE></BODY></HTML>"
diff --git a/netax25.c b/netax25.c
new file mode 100644
index 0000000..65867c9
--- /dev/null
+++ b/netax25.c
@@ -0,0 +1,810 @@
+/* **************************************************************** *
+ * *
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ * NETAX25: Listen on (Linux) AX.25 socket and pick all AX.25 *
+ * data packets ... actually don't pick those *
+ * that are going outwards. All incoming ones do pick. *
+ * *
+ * **************************************************************** */
+
+#include "aprx.h"
+
+
+#ifdef PF_AX25 /* PF_AX25 exists -- highly likely a Linux system ! */
+
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <netpacket/packet.h>
+#include <netinet/if_ether.h>
+
+#include <netinet/in.h>
+
+#include <netax25/ax25.h>
+
+
+/*
+ * Link-level device access
+ *
+ * s = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_AX25));
+ *
+ */
+
+/*
+struct sockaddr_ll
+ {
+ unsigned short int sll_family;
+ unsigned short int sll_protocol;
+ int sll_ifindex;
+ unsigned short int sll_hatype;
+ unsigned char sll_pkttype;
+ unsigned char sll_halen;
+ unsigned char sll_addr[8];
+ };
+
+SOCK_RAW Sending uses sll_ifindex and sll_protocol
+
+*/
+
+struct netax25_dev {
+ int ifindex;
+ int16_t protocol;
+ uint8_t ax25addr[7];
+ uint8_t rxok;
+ //uint8_t txok;
+ uint8_t scan;
+ char devname[IFNAMSIZ];
+ char callsign[10];
+ const struct aprx_interface *interface;
+};
+
+
+static struct netax25_dev **netax25_devs;
+static int netax25_devcount;
+
+
+
+/*
+ * Talking to Linux kernel 2.6.x, using SMACK type frames
+ * on each configured serial port callsign -> ptymux
+ * writer channel. If system does not write correct SMACK
+ * frame on that KISS port for any number of reasons,
+ * including writing incompletely buffered data, then
+ * kernel will be able to notice that frame it received
+ * is not valid, and discards it. (Maybe... P = 2^-16 to
+ * accepting of error frame in spite of these controls.)
+ */
+
+struct netax25_pty {
+ int fd;
+ int ifindex;
+ const char *callsign;
+ const struct aprx_interface *interface;
+ struct sockaddr_ax25 ax25addr;
+};
+
+
+static int rx_socket = -1;
+static int tx_socket = -1;
+
+static struct netax25_pty **ax25rxports;
+static int ax25rxportscount;
+
+static char **ax25ttyports;
+static int *ax25ttyfds;
+static int ax25ttyportscount;
+
+
+
+
+
+#if defined(HAVE_OPENPTY)
+#ifdef HAVE_PTY_H
+#include <pty.h>
+#endif
+
+static void netax25_addttyport(const char *callsign,
+ const int masterfd, const int slavefd);
+
+static const void* netax25_openpty(const char *mycall)
+{
+ int rc;
+ int disc;
+ struct termios tio;
+ char devname[64];
+ uint8_t ax25call[64]; // overlarge for AX.25 - which needs only 7 bytes, but valgrind whines..
+ struct ifreq ifr;
+ int fd = -1;
+ struct netax25_pty *nax25 = NULL;
+ int pty_master, pty_slave;
+
+ if (!mycall)
+ return NULL; /* No mycall, no ptys! */
+
+ memset(ax25call, 0, sizeof(ax25call)); // valgrind
+ if (parse_ax25addr(ax25call, mycall, 0x60)) {
+ // Not valid per AX.25 rules
+ if (debug)
+ printf(" netax25_openpty('%s') failed to parse the parameter string as valid AX.25 callsign. Not opening kernel pty.\n", mycall);
+ return NULL;
+ }
+
+ memset(devname, 0, sizeof(devname)); // valgrind
+ rc = openpty(&pty_master, &pty_slave, devname, NULL, NULL);
+
+ if (debug)
+ printf("openpty() rc=%d name='%s' master=%d slave=%d\n",
+ rc, devname, pty_master, pty_slave);
+
+ if (rc != 0 || pty_slave < 0) {
+ error_exit:;
+ if (pty_master >= 0)
+ close(pty_master);
+ pty_master = -1;
+ if (pty_slave >= 0)
+ close(pty_slave);
+ pty_slave = -1;
+ if (fd >= 0)
+ close(fd);
+ if (debug)
+ printf("netax25_openpty() error exit.\n");
+
+
+ if (nax25 != NULL) free(nax25);
+
+ return NULL; /* D'uh.. */
+ }
+
+ nax25 = calloc( 1,sizeof(*nax25) );
+ nax25->fd = pty_master;
+ nax25->ifindex = -1;
+ nax25->callsign = mycall;
+
+ nax25->ax25addr.sax25_family = PF_AX25;
+ nax25->ax25addr.sax25_ndigis = 0;
+ memcpy(&nax25->ax25addr.sax25_call, ax25call, 7);
+
+ /* setup termios parameters for this line.. */
+ memset(&tio, 0, sizeof(tio)); // please valgrind
+ aprx_cfmakeraw(&tio, 0);
+ tio.c_cc[VMIN] = 1; /* pick at least one char .. */
+ tio.c_cc[VTIME] = 3; /* 0.3 seconds timeout - 36 chars @ 1200 baud */
+ tio.c_cflag |= (CREAD | CLOCAL);
+ cfsetispeed(&tio, B38400); /* Pseudo-tty -- pseudo speed */
+ cfsetospeed(&tio, B38400);
+ rc = tcsetattr(pty_slave, TCSANOW, &tio);
+ if (rc < 0)
+ goto error_exit;
+
+ /* The pty_slave will get N_AX25 discipline attached on itself.. */
+ disc = N_AX25;
+ rc = ioctl(pty_slave, TIOCSETD, &disc);
+ if (rc < 0)
+ goto error_exit;
+
+ rc = ioctl(pty_slave, SIOCGIFNAME, devname);
+ if (rc < 0)
+ goto error_exit;
+
+ /* Convert mycall[] to AX.25 format callsign */
+ rc = ioctl(pty_slave, SIOCSIFHWADDR, ax25call);
+ if (rc < 0)
+ goto error_exit;
+
+ /* Now set encapsulation.. */
+ disc = 4;
+ rc = ioctl(pty_slave, SIOCSIFENCAP, &disc);
+ if (rc < 0)
+ goto error_exit;
+
+ /* Then final tricks to start the interface... */
+ fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd < 0)
+ goto error_exit;
+
+ memset(&ifr, 0, sizeof(ifr)); // please valgrind
+ strncpy(ifr.ifr_name, devname, sizeof(ifr.ifr_name));
+ ifr.ifr_name[sizeof(ifr.ifr_name)-1] = 0;
+
+ ifr.ifr_mtu = 512;
+ rc = ioctl(fd, SIOCSIFMTU, &ifr);
+ if (rc < 0)
+ goto error_exit;
+
+ ifr.ifr_flags = IFF_UP | IFF_RUNNING | IFF_NOARP;
+ rc = ioctl(fd, SIOCSIFFLAGS, &ifr);
+ if (rc < 0)
+ goto error_exit;
+
+ close(fd);
+
+ /* OK, we write and read on pty_master, the pty_slave is now
+ attached on kernel side AX.25 interface with call: mycall */
+
+ netax25_addttyport( mycall, pty_master, pty_slave );
+
+ return (void*) nax25;
+}
+
+void netax25_sendax25(const void *nax25p, const void *ax25, int ax25len)
+{
+ int rc, p;
+ uint8_t ax25buf[2100];
+ const struct netax25_pty *nax25 = nax25p;
+
+ /* kissencoder() takes AX.25 frame, and adds framing + cmd-byte */
+ rc = kissencoder(ax25buf, sizeof(ax25buf), LINETYPE_KISSSMACK,
+ ax25, ax25len, 0x80);
+ if (rc < 0)
+ return;
+ ax25len = rc;
+
+ if (debug>2) {
+ printf("netax25_sendax25() len=%d ",ax25len);
+ hexdumpfp(stdout, ax25, ax25len, 1);
+ printf("\n");
+ }
+
+
+ /* Try to write it to the PTY */
+ p = 0;
+ rc = write(nax25->fd, ax25buf + p, ax25len - p);
+ if (rc < 0) rc = 0; // error hickup..
+ p += rc; rc = 0;
+ if (p < ax25len) { // something left unwritten
+ rc = write(nax25->fd, ax25buf + p, ax25len - p);
+ if (rc < 0) rc = 0; // error hickup..
+ }
+ p += rc; rc = 0;
+ if (p < ax25len) { // something left unwritten
+ rc = write(nax25->fd, ax25buf + p, ax25len - p);
+ if (rc < 0) rc = 0; // error hickup..
+ }
+ p += rc; rc = 0;
+ // Now it either succeeded, or it failed.
+ // in both cases we give up on this frame.
+ if (p < ax25len) {
+ if (aprxlogfile) {
+ aprxlog("netax25_sendax25(%s,len=%d) wrote %d bytes\n", nax25->callsign, ax25len, p);
+ }
+ }
+}
+
+#else /* !HAVE_OPENPTY */
+
+static const void* netax25_openpty(const char *mycall)
+{
+ return NULL;
+}
+
+void netax25_sendax25(const void *nax25, const void *ax25, int ax25len)
+{
+}
+#endif /* HAVE_OPENPTY */
+
+static int is_ax25ttyport(const char *callsign)
+{
+ int i;
+ for (i = 0; i < ax25ttyportscount; ++i) {
+ if (strcmp(callsign,ax25ttyports[i]) == 0)
+ return 1; // Have match
+ }
+ return 0; // No match
+}
+
+
+static int scan_linux_devices(void) {
+ FILE *fp;
+ struct ifreq ifr;
+ char buffer[512], *s;
+ int fd;
+ struct netax25_dev ax25dev, *d;
+ int i;
+
+ // Mark all devices ready for scanning
+ for (i = 0; i < netax25_devcount; ++i)
+ netax25_devs[i]->scan = 0;
+
+ fd = socket(PF_FILE, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ // ... error
+ if (debug)printf("Can not create socket(PF_FILE,SOCK_DGRAM,0); errno=%d\n", errno);
+ return -1;
+ }
+ fp = fopen("/proc/net/dev", "r");
+ if (fp == NULL) {
+ if (debug)printf("Can not open /proc/net/dev for reading; errno=%d\n", errno);
+ close(fd);
+ // ... error
+ return -1;
+ }
+ // Two header lines
+ s = fgets(buffer, sizeof(buffer), fp);
+ s = fgets(buffer, sizeof(buffer), fp);
+ // Then network interface names
+ while (!feof(fp)) {
+ if (!fgets(buffer, sizeof(buffer), fp))
+ break; // EOF
+ s = strchr(buffer, ':');
+ if (s) *s = 0;
+ s = buffer;
+ while (*s == ' '||*s == '\t') ++s;
+ memset(&ifr, 0, sizeof(ifr)); // please valgrind
+ strncpy(ifr.ifr_name, s, IFNAMSIZ-1);
+ ifr.ifr_name[IFNAMSIZ-1] = 0;
+
+ // Is it active?
+ if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) {
+ // error
+ continue;
+ }
+ if (!(ifr.ifr_flags & IFF_UP))
+ continue; // not active, try next
+
+ // Does it have AX.25 HW address ?
+ if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) {
+ // Error
+ continue;
+ }
+ if (ifr.ifr_hwaddr.sa_family != ARPHRD_AX25)
+ continue; // Not AX.25 HW address, try next
+
+ memset(&ax25dev, 0, sizeof(ax25dev));
+ memcpy(ax25dev.devname, ifr.ifr_name, IFNAMSIZ);
+ memcpy(ax25dev.ax25addr, ifr.ifr_hwaddr.sa_data, 7); // AX.25 address
+ ax25_to_tnc2_fmtaddress(ax25dev.callsign, ax25dev.ax25addr, 0); // in text
+
+ if (ioctl(fd, SIOCGIFINDEX, &ifr) < 0) {
+ // Error
+ continue;
+ }
+ ax25dev.ifindex = ifr.ifr_ifindex;
+
+ // Store/Update internal kernel interface index list
+
+ d = NULL;
+ for (i = 0; i < netax25_devcount; ++i) {
+ d = netax25_devs[i];
+ if (d->ifindex == ax25dev.ifindex) {
+ d->scan = 1; // The ifindex does not change during interface lifetime
+ break;
+ }
+ d = NULL;
+ }
+ if (d == NULL) {
+ // Not in known interfaces, add a new one..
+ d = malloc(sizeof(*d));
+ ++netax25_devcount;
+ netax25_devs = realloc( netax25_devs,
+ sizeof(void*) * netax25_devcount );
+ netax25_devs[netax25_devcount-1] = d;
+ memcpy(d, &ax25dev, sizeof(*d));
+ d->scan = 1;
+ d->rxok = !is_ax25ttyport(d->callsign);
+ }
+
+ }
+ fclose(fp);
+ close(fd);
+ // Remove devices no longer known
+ for (i = 0; i < netax25_devcount; ++i) {
+ if (netax25_devs[i]->scan == 0) {
+ int j;
+ if (debug>1)printf("Compating netax25_devs[] i=%d callsign=%s\n",
+ i, netax25_devs[i]->callsign);
+ free(netax25_devs[i]);
+ for (j = i+1; j < netax25_devcount; ++j) {
+ netax25_devs[j-1] = netax25_devs[j];
+ }
+ --netax25_devcount;
+ }
+ }
+
+ // Link interfaces
+ for (i = 0; i < netax25_devcount; ++i) {
+ int j;
+ struct netax25_dev *d = netax25_devs[i];
+ for (j = 0; j < ax25rxportscount; ++j) {
+ if (strcmp(ax25rxports[j]->callsign,d->callsign) == 0) {
+ d->interface = ax25rxports[j]->interface;
+ ax25rxports[j]->ifindex = d->ifindex;
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+
+
+/* config interface: ax25-rxport: callsign */
+void *netax25_addrxport(const char *callsign, const struct aprx_interface *interface)
+{
+ struct netax25_pty *nax25p = calloc(1, sizeof(*nax25p));
+
+ nax25p->fd = -1;
+ nax25p->interface = interface;
+ nax25p->ax25addr.sax25_family = PF_AX25;
+ nax25p->ax25addr.sax25_ndigis = 0;
+
+ if (interface == NULL) { // Old config style
+ if (parse_ax25addr((uint8_t*)&nax25p->ax25addr.sax25_call, callsign, 0x60)) {
+ // Not valid per AX.25 rules
+ free(nax25p);
+ return NULL;
+ }
+ nax25p->callsign = strdup(callsign);
+ } else { // new config fule
+ memcpy(&nax25p->ax25addr.sax25_call, interface->ax25call, sizeof(interface->ax25call));
+ nax25p->callsign = interface->callsign;
+ }
+
+ ax25rxports = realloc(ax25rxports,
+ sizeof(struct netax25_pty*) * (ax25rxportscount + 1));
+ ax25rxports[ax25rxportscount++] = nax25p;
+
+ return nax25p;
+}
+
+static void netax25_addttyport(const char *callsign,
+ const int masterfd, const int slavefd)
+{
+ ax25ttyports = realloc(ax25ttyports,
+ sizeof(void *) * (ax25ttyportscount + 1));
+ ax25ttyfds = realloc(ax25ttyfds,
+ sizeof(int) * (ax25ttyportscount + 1));
+ ax25ttyports[ax25ttyportscount] = strdup(callsign);
+ ax25ttyfds [ax25ttyportscount] = masterfd; /* slavefd forgotten */
+ ++ax25ttyportscount;
+}
+
+
+/* Nothing much in early init */
+void netax25_init(void)
+{
+}
+
+/* .. but all things in late start.. */
+void netax25_start(void)
+{
+ int i;
+ int rx_protocol;
+
+ rx_socket = -1; /* Initialize for early bail-out */
+ tx_socket = -1;
+
+ if (!ax25rxports) return; /* No configured receiver ports.
+ No receiver socket creation. */
+
+ rx_protocol = ETH_P_AX25; /* Choosing ETH_P_ALL would pick also
+ outbound packets, but also all of
+ the ethernet traffic.. ETH_P_AX25
+ picks only inbound-at-ax25-devices
+ ..packets. */
+
+ rx_socket = socket(PF_PACKET, SOCK_RAW, htons(rx_protocol));
+ tx_socket = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_AX25));
+
+ if (rx_socket < 0) {
+ i = errno;
+ /* D'uh.. could not open it, report and leave it at that. */
+ fprintf(stderr,
+ "aprx: Could not open socket(PF_PACKET,SOCK_RAW,ETH_P_AX25) for listening. Errno=%d (%s)"
+ " -- not a big deal unless you want to receive via AX.25 sockets.\n",
+ i, strerror(i));
+ return;
+ }
+
+ if (tx_socket < 0) {
+ i = errno;
+ /* D'uh.. could not open it, report and leave it at that. */
+ fprintf(stderr,
+ "aprx: Could not open socket(PF_PACKET,SOCK_RAW,ETH_P_AX25) for sending. Errno=%d (%s)"
+ " -- not a big deal unless you want to send via AX.25 sockets.\n",
+ i, strerror(i));
+ return;
+ }
+
+ if (rx_socket >= 0)
+ fd_nonblockingmode(rx_socket);
+}
+
+
+/* .. but all things in late start.. */
+const void* netax25_open(const char *ifcallsign)
+{
+ return netax25_openpty(ifcallsign);
+}
+
+static struct timeval next_scantime;
+
+static void netax25_resettimer(void*arg)
+{
+ struct timeval *tv = (struct timeval *)arg;
+ tv_timeradd_seconds(tv, &tick, 60);
+ scan_linux_devices();
+}
+
+int netax25_prepoll(struct aprxpolls *app)
+{
+ struct pollfd *pfd;
+ int i;
+
+ if (next_scantime.tv_sec == 0) next_scantime = tick;
+
+ if (time_reset) {
+ netax25_resettimer(&next_scantime);
+ }
+
+ if (rx_socket >= 0) {
+ /* FD is open, lets mark it for poll read.. */
+ pfd = aprxpolls_new(app);
+ pfd->fd = rx_socket;
+ pfd->events = POLLIN | POLLPRI;
+ pfd->revents = 0;
+ }
+
+ /* read from PTY masters */
+ for (i = 0; i < ax25ttyportscount; ++i) {
+ if (ax25ttyfds[i] >= 0) {
+ pfd = aprxpolls_new(app);
+ pfd->fd = ax25ttyfds[i];
+ pfd->events = POLLIN | POLLPRI;
+ pfd->revents = 0;
+ }
+ }
+
+ return 1;
+}
+
+static int rxsock_read( const int fd )
+{
+ struct sockaddr_ll sll;
+ socklen_t sllsize;
+ int rcvlen, ifindex, i;
+ struct netax25_dev *netdev;
+ uint8_t rxbuf[3000];
+
+ sllsize = sizeof(sll);
+ rcvlen = recvfrom(fd, rxbuf, sizeof(rxbuf), 0, (struct sockaddr*)&sll, &sllsize);
+
+ if (rcvlen < 0) {
+ return 0; /* No more at this time.. */
+ }
+
+/*
+struct sockaddr_ll
+ {
+ unsigned short int sll_family; = PF_PACKET
+ unsigned short int sll_protocol; = 200 ?
+ int sll_ifindex; = 4
+ unsigned short int sll_hatype; = 3 = SOCK_RAW ?
+ unsigned char sll_pkttype; = 0
+ unsigned char sll_halen; = 0
+ unsigned char sll_addr[8]; = random
+ };
+
+netax25rx packet len=54 from rx_socket; family=17 protocol=200 ifindex=4 hatype=3
+ pkttype=0 halen=0 addr=84:f9:ca:bf:d7:04:f3:b7
+
+Data: 00 82 a0 aa 64 6a 9c e0 9e 90 70 9a b0 94 60 ae 92 88 8a 64 40 61 03 f0 3d 36 33 35 33 2e ...
+Text: 00 82 a0 aa d j 9c e0 9e 90 p 9a b0 94 ` ae 92 88 8a d @ a 03 f0 = 6 3 5 3 . ...
+AX25: 00 A P U 2 5 N p O H 8 M X J 0 W I D E 2 0 01 x 1e 1b 19 1a 19 17 ...
+
+Leads with 00 byte, then AX.25 address..
+
+*/
+
+ if (sll.sll_family != PF_PACKET ||
+ sll.sll_protocol != htons(ETH_P_AX25) ||
+ sll.sll_hatype != SOCK_RAW ||
+ sll.sll_pkttype != 0 ||
+ sll.sll_halen != 0 ||
+ rxbuf[0] != 0 ) {
+ return 1; // Not of our interest
+ }
+ ifindex = sll.sll_ifindex;
+
+ if (debug>1) {
+ printf("netax25rx packet len=%d from rx_socket; family=%d protocol=%x ifindex=%d hatype=%d pkttype=%d halen=%d\n",
+ rcvlen, sll.sll_family, sll.sll_protocol, sll.sll_ifindex, sll.sll_hatype, sll.sll_pkttype, sll.sll_halen);
+/*
+ printf(" addr=%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n",
+ sll.sll_addr[0],sll.sll_addr[1],sll.sll_addr[2],sll.sll_addr[3],
+ sll.sll_addr[4],sll.sll_addr[5],sll.sll_addr[6],sll.sll_addr[7]);
+
+ int i;
+ printf("Data: ");
+ for (i = 0; i < rcvlen; ++i)
+ printf(" %02x", rxbuf[i]);
+ printf("\n");
+ printf("Text: ");
+ for (i = 0; i < rcvlen; ++i) {
+ uint8_t c = rxbuf[i];
+ if (32 <= c && c <= 126)
+ printf(" %c", c);
+ else
+ printf(" %02x", c);
+ }
+ printf("\n");
+ printf("AX25: ");
+ for (i = 0; i < rcvlen; ++i) {
+ uint8_t c = rxbuf[i] >> 1;
+ if (32 <= c && c <= 126)
+ printf(" %c", c);
+ else
+ printf(" %02x", c);
+ }
+ printf("\n");
+*/
+ }
+
+ netdev = NULL;
+ for (i = 0; i < netax25_devcount; ++i) {
+ if (netax25_devs[i]->ifindex == ifindex) {
+ netdev = netax25_devs[i];
+ break;
+ }
+ }
+ if (netdev == NULL) {
+ // Not found from Ax.25 devices
+ if (debug>1) printf(".. not from known AX.25 device\n");
+ return 1;
+ }
+ if (netdev->interface == NULL) {
+ if (debug>1) printf(".. not from AX.25 device configured for receiving.\n");
+ return 1;
+ }
+
+ if (debug) printf("Received frame of %d bytes from %s: %s\n",
+ rcvlen, netdev->devname, netdev->callsign);
+
+ // if (is_ax25ttyport(netdev->callsign)) {
+ if (!netdev->rxok) {
+ if (debug > 1) {
+ printf("%s is ttyport which we serve.\n",netdev->callsign);
+ }
+ return 1; // We drop our own packets, if we ever see them
+ }
+
+ /// Now: actual AX.25 frame reception,
+ // and transmit via ax25_to_tnc2() !
+
+ /*
+ * "+10" is a magic constant for trying
+ * to estimate channel occupation overhead
+ */
+ erlang_add(netdev->callsign, ERLANG_RX, rcvlen + 10, 1); // rxsock_read()
+
+ // Send it to Rx-IGate, validates also AX.25 header bits,
+ // and returns non-zero only when things are OK for processing.
+ // Will internally also send to interface layer, if OK.
+ if (ax25_to_tnc2(netdev->interface, netdev->callsign, 0, rxbuf[0], rxbuf + 1, rcvlen - 1)) {
+ // The packet is valid per AX.25 header bit rules.
+ // ax25_to_tnc2() did send the packet to rx-igate
+ ;
+ } else {
+ // The packet is not valid per AX.25 header bit rules
+ erlang_add(netdev->callsign, ERLANG_DROP, rcvlen+10, 1); /* Account one packet */
+
+ if (aprxlogfile) {
+ FILE *fp = fopen(aprxlogfile, "a");
+ if (fp) {
+ char timebuf[60];
+ printtime(timebuf, sizeof(timebuf));
+
+ fprintf(fp, "%s ax25_to_tnc2(%s,len=%d) rejected the message: ", timebuf, netdev->callsign, rcvlen);
+ hexdumpfp(fp, rxbuf, rcvlen, 1);
+ fprintf(fp, "\n");
+ fclose(fp);
+ }
+ }
+ }
+
+ return 1;
+}
+
+static void discard_read_fd( const int fd )
+{
+ char buf[2000];
+ (void)read(fd, buf, sizeof(buf));
+}
+
+
+int netax25_postpoll(struct aprxpolls *app)
+{
+ int i, j;
+ struct pollfd *pfd;
+ // char ifaddress[10];
+
+ assert(app->polls != NULL);
+
+ if (tv_timercmp(&tick, &next_scantime) > 0) {
+ scan_linux_devices();
+ // Rescan every 60 seconds, on the dot.
+ tv_timeradd_seconds(&next_scantime, &next_scantime, 60);
+ }
+
+ pfd = app->polls;
+
+ if (rx_socket < 0)
+ return 0;
+
+ for (i = 0; i < app->pollcount; ++i, ++pfd) {
+ if ((pfd->fd == rx_socket) &&
+ (pfd->revents & (POLLIN | POLLPRI))) {
+ /* something coming in.. */
+ rxsock_read( rx_socket );
+ }
+ for (j = 0; j < ax25ttyportscount; ++j) {
+ if ((pfd->revents & (POLLIN | POLLPRI)) &&
+ (ax25ttyfds[j] == pfd->fd)) {
+ discard_read_fd(ax25ttyfds[j]);
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+
+void netax25_sendto(const void *nax25p, const uint8_t *axaddr, const int axaddrlen, const char *axdata, const int axdatalen)
+{
+ const struct netax25_pty *nax25 = nax25p;
+ struct sockaddr_ll sll;
+ char c0[1];
+ struct iovec iovec[3];
+ struct msghdr mh;
+ int i, len;
+
+ if (tx_socket < 0) {
+ if (debug>1) printf("netax25_sendto() tx_socket = -1, can not do..\n");
+ return; // D'uh..
+ }
+ if (nax25->ifindex < 0) {
+ if (debug>1) printf("netax25_sendto() ifindex < 0, can not do..\n");
+ return; // D'uh..
+ }
+
+ if (debug>2) {
+ printf("netax25_sendto() len=%d,%d ",axaddrlen,axdatalen);
+ hexdumpfp(stdout, axaddr, axaddrlen, 1);
+ printf(" // ");
+ hexdumpfp(stdout, (uint8_t*)axdata, axdatalen, 0);
+ printf("\n");
+ }
+
+ memset(&sll, 0, sizeof(sll));
+ sll.sll_family = PF_PACKET;
+ sll.sll_ifindex = nax25->ifindex;
+ sll.sll_protocol = htons(ETH_P_AX25);
+ sll.sll_hatype = SOCK_RAW;
+
+ c0[0] = 0;
+ iovec[0].iov_base = c0;
+ iovec[0].iov_len = 1;
+ iovec[1].iov_base = (void*)axaddr; // silence the compiler
+ iovec[1].iov_len = axaddrlen;
+ iovec[2].iov_base = (void*)axdata; // silence the compiler
+ iovec[2].iov_len = axdatalen;
+ len = 1+axaddrlen+axdatalen; // for debugging
+
+ memset(&mh, 0, sizeof(mh));
+ mh.msg_name = &sll;
+ mh.msg_namelen = sizeof(sll);
+ mh.msg_iov = iovec;
+ mh.msg_iovlen = 3;
+
+ errno = 0;
+ i = sendmsg(tx_socket, &mh, 0);
+ if (debug>1)printf("netax25_sendto() the sendmsg len=%d rc=%d errno=%d\n", len, i, errno);
+
+ erlang_add(nax25->callsign, ERLANG_TX, axaddrlen+axdatalen + 10, 1); // netax25_sendto()
+}
+#endif
diff --git a/netresolver.c b/netresolver.c
new file mode 100644
index 0000000..85960f2
--- /dev/null
+++ b/netresolver.c
@@ -0,0 +1,151 @@
+/* **************************************************************** *
+ * *
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ * **************************************************************** */
+#include "aprx.h"
+
+#if defined(HAVE_PTHREAD_CREATE) && defined(ENABLE_PTHREAD)
+#include <signal.h>
+#include <pthread.h>
+pthread_t netresolv_thread;
+pthread_attr_t pthr_attrs;
+#endif
+
+static int nrcount;
+static struct netresolver **nr;
+static int netresolv_die_now;
+
+static int RE_RESOLVE_INTERVAL = 300; // 15 minutes ?
+
+struct netresolver *netresolv_add(const char *hostname, const char *port) {
+ struct netresolver *n = malloc(sizeof(*n));
+ memset(n, 0, sizeof(*n));
+ n->hostname = hostname;
+ n->port = port;
+ n->ai.ai_addr = &n->sa;
+
+ ++nrcount;
+ nr = realloc(nr, sizeof(void*)*nrcount);
+ nr[nrcount-1] = n;
+ return n;
+}
+
+
+static void resolve_all(void) {
+ int i;
+
+ if (debug>1)
+ printf("netresolve nrcount=%d\n", nrcount);
+
+ for (i = 0; i < nrcount; ++i) {
+ struct netresolver *n = nr[i];
+ struct addrinfo *ai, req;
+ int rc;
+
+ timetick();
+
+ if (timecmp(n->re_resolve_time, tick.tv_sec) > 0) {
+ // Not yet to re-resolve this one
+ if (debug>1)
+ printf("nr[%d] re_resolve_time in future (%d secs)\n",
+ i, (int)(n->re_resolve_time - tick.tv_sec));
+ continue;
+ }
+
+ memset(&req, 0, sizeof(req));
+ req.ai_socktype = SOCK_STREAM;
+ req.ai_protocol = IPPROTO_TCP;
+ req.ai_flags = 0;
+#if 1
+ req.ai_family = AF_UNSPEC; /* IPv4 and IPv6 are both OK */
+#else
+ req.ai_family = AF_INET; /* IPv4 only */
+#endif
+ ai = NULL;
+
+ rc = getaddrinfo(n->hostname, n->port, &req, &ai);
+ if (rc != 0) {
+ // re-resolving failed, discard possible junk result
+ if (debug>1)
+ printf("nr[%d] resolving of %s:%s failed, error: %s\n",
+ i, n->hostname, n->port, gai_strerror(errno));
+ if (ai != NULL)
+ freeaddrinfo(ai);
+ continue;
+ }
+
+ if (debug>1)
+ printf("nr[%d] resolving of %s:%s success!\n",
+ i, n->hostname, n->port);
+
+ timetick();
+
+ // Make local static copy of first result
+ memcpy(&n->sa, ai->ai_addr, ai->ai_addrlen);
+ n->ai.ai_flags = ai->ai_flags;
+ n->ai.ai_family = ai->ai_family;
+ n->ai.ai_socktype = ai->ai_socktype;
+ n->ai.ai_protocol = ai->ai_protocol;
+ n->ai.ai_addrlen = ai->ai_addrlen;
+ n->ai.ai_addrlen = ai->ai_addrlen;
+
+ freeaddrinfo(ai);
+ n->re_resolve_time = tick.tv_sec + RE_RESOLVE_INTERVAL;
+ }
+}
+
+
+#if defined(HAVE_PTHREAD_CREATE) && defined(ENABLE_PTHREAD)
+static void netresolv_runthread(void) {
+ sigset_t sigs_to_block;
+
+ sigemptyset(&sigs_to_block);
+ sigaddset(&sigs_to_block, SIGALRM);
+ sigaddset(&sigs_to_block, SIGINT);
+ sigaddset(&sigs_to_block, SIGTERM);
+ sigaddset(&sigs_to_block, SIGQUIT);
+ sigaddset(&sigs_to_block, SIGHUP);
+ sigaddset(&sigs_to_block, SIGURG);
+ sigaddset(&sigs_to_block, SIGPIPE);
+ sigaddset(&sigs_to_block, SIGUSR1);
+ pthread_sigmask(SIG_BLOCK, &sigs_to_block, NULL);
+
+ // the main program can cancel us at will
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+
+ while (!die_now) {
+ poll(NULL, 0, 30000); // Sleep 30 seconds (in a reliable way)
+ resolve_all();
+ }
+}
+#endif
+
+// Start netresolver thread, but at first run one round of resolving!
+void netresolv_start(void) {
+ resolve_all();
+
+#if defined(HAVE_PTHREAD_CREATE) && defined(ENABLE_PTHREAD)
+ pthread_attr_init(&pthr_attrs);
+ /* 64 kB stack is enough for this thread (I hope!)
+ default of 2 MB is way too much...*/
+ pthread_attr_setstacksize(&pthr_attrs, 64*1024);
+
+ pthread_create(&netresolv_thread, &pthr_attrs, (void*)netresolv_runthread, NULL);
+
+#endif
+}
+
+// Shutdown the netresolver thread
+void netresolv_stop(void)
+{
+ die_now = 1;
+#if defined(HAVE_PTHREAD_CREATE) && defined(ENABLE_PTHREAD)
+ pthread_cancel(netresolv_thread);
+ pthread_join(netresolv_thread, NULL);
+#endif
+}
diff --git a/parse_aprs.c b/parse_aprs.c
new file mode 100644
index 0000000..f8b0bd3
--- /dev/null
+++ b/parse_aprs.c
@@ -0,0 +1,1416 @@
+/*
+ * aprsc
+ *
+ * (c) Heikki Hannikainen, OH7LZB <hessu at hes.iki.fi>
+ *
+ * This program is licensed under the BSD license, which can be found
+ * in the file LICENSE.
+ *
+ */
+
+/*
+ * A simple APRS parser for aprsc. Translated from Ham::APRS::FAP
+ * perl module (by OH2KKU).
+ *
+ * Only needs to get lat/lng out of the packet, other features would
+ * be unnecessary in this application, and slow down the parser.
+ * ... but lets still classify the packet, output filter needs that.
+ *
+ */
+
+#include "aprx.h"
+#include <math.h>
+
+#define DEBUG_LOG(...) if(debug)printf(__VA_ARGS__)
+
+
+/*
+ * Check if the given character is a valid symbol table identifier
+ * or an overlay character. The set is different for compressed
+ * and uncompressed packets - the former has the overlaid number (0-9)
+ * replaced with n-j.
+ */
+
+static int valid_sym_table_compressed(char c)
+{
+ return (c == '/' || c == '\\' || (c >= 0x41 && c <= 0x5A)
+ || (c >= 0x61 && c <= 0x6A)); /* [\/\\A-Za-j] */
+}
+
+static int valid_sym_table_uncompressed(char c)
+{
+ return (c == '/' || c == '\\' || (c >= 0x41 && c <= 0x5A)
+ || (c >= 0x30 && c <= 0x39)); /* [\/\\A-Z0-9] */
+}
+
+/*
+ * Fill the pbuf_t structure with a parsed position and
+ * symbol table & code. Also does range checking for lat/lng
+ * and pre-calculates cosf(lat) for range filters.
+ */
+
+static int pbuf_fill_pos(struct pbuf_t *pb, const float lat, const float lng, const char sym_table, const char sym_code)
+{
+ int bad = 0;
+ /* symbol table and code */
+ pb->symbol[0] = sym_table;
+ pb->symbol[1] = sym_code;
+ pb->symbol[2] = 0;
+
+ /* Is it perhaps a weather report ? */
+ if (sym_code == '_' && (sym_table == '/' || sym_table == '\\'))
+ pb->packettype |= T_WX;
+ if (sym_code == '@' && (sym_table == '/' || sym_table == '\\'))
+ pb->packettype |= T_WX; /* Hurricane */
+
+ bad |= (lat < -89.9 && -0.0001 <= lng && lng <= 0.0001);
+ bad |= (lat > 89.9 && -0.0001 <= lng && lng <= 0.0001);
+
+ if (-0.0001 <= lat && lat <= 0.0001) {
+ bad |= ( -0.0001 <= lng && lng <= 0.0001);
+ bad |= ( -90.01 <= lng && lng <= -89.99);
+ bad |= ( 89.99 <= lng && lng <= 90.01);
+ }
+
+
+ if (bad || lat < -90.0 || lat > 90.0 || lng < -180.0 || lng > 180.0) {
+ if (debug)
+ printf("\tposition out of range: lat %.3f lng %.3f", lat, lng);
+
+ return 0; /* out of range */
+ }
+
+ if (debug)
+ printf("\tposition ok: lat %.3f lng %.3f", lat, lng);
+
+ /* Pre-calculations for A/R/F/M-filter tests */
+ pb->lat = filter_lat2rad(lat); /* deg-to-radians */
+ pb->cos_lat = cosf(pb->lat); /* used in range filters */
+ pb->lng = filter_lon2rad(lng); /* deg-to-radians */
+
+ pb->flags |= F_HASPOS; /* the packet has positional data */
+
+ return 1;
+}
+
+/*
+ * Parse symbol from destination callsign
+ */
+
+static int get_symbol_from_dstcall_twochar(const char c1, const char c2, char *sym_table, char *sym_code)
+{
+ //hlog(LOG_DEBUG, "\ttwochar %c %c", c1, c2);
+ if (c1 == 'B') {
+ if (c2 >= 'B' && c2 <= 'P') {
+ *sym_table = '/';
+ *sym_code = c2 - 'B' + '!';
+ return 1;
+ }
+ return 0;
+ }
+
+ if (c1 == 'P') {
+ if (c2 >= '0' && c2 <= '9') {
+ *sym_table = '/';
+ *sym_code = c2;
+ return 1;
+ }
+ if (c2 >= 'A' && c2 <= 'Z') {
+ *sym_table = '/';
+ *sym_code = c2;
+ return 1;
+ }
+ return 0;
+ }
+
+ if (c1 == 'M') {
+ if (c2 >= 'R' && c2 <= 'X') {
+ *sym_table = '/';
+ *sym_code = c2 - 'R' + ':';
+ return 1;
+ }
+ return 0;
+ }
+
+ if (c1 == 'H') {
+ if (c2 >= 'S' && c2 <= 'X') {
+ *sym_table = '/';
+ *sym_code = c2 - 'S' + '[';
+ return 1;
+ }
+ return 0;
+ }
+
+ if (c1 == 'L') {
+ if (c2 >= 'A' && c2 <= 'Z') {
+ *sym_table = '/';
+ *sym_code = c2 - 'A' + 'a';
+ return 1;
+ }
+ return 0;
+ }
+
+ if (c1 == 'J') {
+ if (c2 >= '1' && c2 <= '4') {
+ *sym_table = '/';
+ *sym_code = c2 - '1' + '{';
+ return 1;
+ }
+ return 0;
+ }
+
+ if (c1 == 'O') {
+ if (c2 >= 'B' && c2 <= 'P') {
+ *sym_table = '\\';
+ *sym_code = c2 - 'B' + '!';
+ return 1;
+ }
+ return 0;
+ }
+
+ if (c1 == 'A') {
+ if (c2 >= '0' && c2 <= '9') {
+ *sym_table = '\\';
+ *sym_code = c2;
+ return 1;
+ }
+ if (c2 >= 'A' && c2 <= 'Z') {
+ *sym_table = '\\';
+ *sym_code = c2;
+ return 1;
+ }
+ return 0;
+ }
+
+ if (c1 == 'N') {
+ if (c2 >= 'R' && c2 <= 'X') {
+ *sym_table = '\\';
+ *sym_code = c2 - 'R' + ':';
+ return 1;
+ }
+ return 0;
+ }
+
+ if (c1 == 'D') {
+ if (c2 >= 'S' && c2 <= 'X') {
+ *sym_table = '\\';
+ *sym_code = c2 - 'S' + '[';
+ return 1;
+ }
+ return 0;
+ }
+
+ if (c1 == 'S') {
+ if (c2 >= 'A' && c2 <= 'Z') {
+ *sym_table = '\\';
+ *sym_code = c2 - 'A' + 'a';
+ return 1;
+ }
+ return 0;
+ }
+
+ if (c1 == 'Q') {
+ if (c2 >= '1' && c2 <= '4') {
+ *sym_table = '\\';
+ *sym_code = c2 - '1' + '{';
+ return 1;
+ }
+ return 0;
+ }
+
+ return 0;
+}
+
+static int get_symbol_from_dstcall(struct pbuf_t *pb, char *sym_table, char *sym_code)
+{
+ const char *d_start;
+ char type;
+ char overlay;
+ int sublength;
+ int numberid;
+
+ /* check that the destination call exists and is of the right size for symbol */
+ d_start = pb->srccall_end+1;
+ if (pb->dstcall_end_or_ssid - d_start < 5)
+ return 0; /* too short */
+
+ /* length of the parsed string */
+ sublength = pb->dstcall_end_or_ssid - d_start - 3;
+ if (sublength > 3)
+ sublength = 3;
+
+#ifdef DEBUG_PARSE_APRS
+ if (debug)
+ printf("\tget_symbol_from_dstcall: %.*s (%d)", (int)(pb->dstcall_end_or_ssid - d_start), d_start, sublength);
+#endif
+
+ if (strncmp(d_start, "GPS", 3) != 0 && strncmp(d_start, "SPC", 3) != 0 && strncmp(d_start, "SYM", 3) != 0)
+ return 0;
+
+ // hlog(LOG_DEBUG, "\ttesting %c %c %c", d_start[3], d_start[4], d_start[5]);
+ if (!isalnum(d_start[3]) || !isalnum(d_start[4]))
+ return 0;
+
+ if (sublength == 3 && !isalnum(d_start[5]))
+ return 0;
+
+ type = d_start[3];
+
+ if (sublength == 3) {
+ if (type == 'C' || type == 'E') {
+ if (!isdigit(d_start[4]))
+ return 0;
+ if (!isdigit(d_start[5]))
+ return 0;
+ numberid = (d_start[4] - 48) * 10 + (d_start[5] - 48);
+
+ *sym_code = numberid + 32;
+ if (type == 'C')
+ *sym_table = '/';
+ else
+ *sym_table = '\\';
+
+#ifdef DEBUG_PARSE_APRS
+ if (debug)
+ printf("\tnumeric symbol id in dstcall: %.*s: table %c code %c",
+ (int)(pb->dstcall_end_or_ssid - d_start - 3), d_start + 3, *sym_table, *sym_code);
+#endif
+ return 1;
+ } else {
+ /* secondary symbol table, with overlay
+ * Check first that we really are in the secondary symbol table
+ */
+ overlay = d_start[5];
+ if ((type == 'O' || type == 'A' || type == 'N' ||
+ type == 'D' || type == 'S' || type == 'Q')
+ && isalnum(overlay)) {
+ return get_symbol_from_dstcall_twochar(d_start[3], d_start[4], sym_table, sym_code);
+ }
+ return 0;
+ }
+ } else {
+ // primary or secondary table, no overlay
+ return get_symbol_from_dstcall_twochar(d_start[3], d_start[4], sym_table, sym_code);
+ }
+
+ return 0;
+}
+
+
+/*
+ * Parse NMEA position packets.
+ */
+
+static int parse_aprs_nmea(struct pbuf_t *pb, const char *body, const char *body_end)
+{
+ float lat, lng;
+ const char *latp, *lngp;
+ int i, la, lo;
+ char lac, loc;
+ char sym_table, sym_code;
+
+ if (memcmp(body,"ULT",3) == 0) {
+ /* Ah.. "$ULT..." - that is, Ultimeter 2000 weather instrument */
+ pb->packettype |= T_WX;
+ return 1;
+ }
+
+ lat = lng = 0.0;
+ latp = lngp = NULL;
+
+ /* NMEA sentences to understand:
+ $GPGGA Global Positioning System Fix Data
+ $GPGLL Geographic Position, Latitude/Longitude Data
+ $GPRMC Remommended Minimum Specific GPS/Transit Data
+ $GPWPT Way Point Location ?? (bug in APRS specs ?)
+ $GPWPL Waypoint Load (not in APRS specs, but in NMEA specs)
+ $PNTS Seen on APRS-IS, private sentense based on NMEA..
+ $xxTLL Not seen on radio network, usually $RATLL - Target positions
+ reported by RAdar.
+ */
+
+ if (memcmp(body, "GPGGA,", 6) == 0) {
+ /* GPGGA,175059,3347.4969,N,11805.7319,W,2,12,1.0,6.8,M,-32.1,M,,*7D
+ // v=1, looks fine
+ // GPGGA,000000,5132.038,N,11310.221,W,1,09,0.8,940.0,M,-17.7,,
+ // v=1, timestamp odd, coords look fine
+ // GPGGA,,,,,,0,00,,,,,,,*66
+ // v=0, invalid
+ // GPGGA,121230,4518.7931,N,07322.3202,W,2,08,1.0,40.0,M,-32.4,M,,*46
+ // v=2, looks valid ?
+ // GPGGA,193115.00,3302.50182,N,11651.22581,W,1,08,01.6,00465.90,M,-32.891,M,,*5F
+ // $GPGGA,hhmmss.dd,xxmm.dddd,<N|S>,yyymm.dddd,<E|W>,v,
+ // ss,d.d,h.h,M,g.g,M,a.a,xxxx*hh<CR><LF>
+ */
+
+ latp = body+6; // over the keyword
+ while (latp < body_end && *latp != ',')
+ latp++; // scan over the timestamp
+ if (*latp == ',')
+ latp++; // .. and into latitude.
+ lngp = latp;
+ while (lngp < body_end && *lngp != ',')
+ lngp++;
+ if (*lngp == ',')
+ lngp++;
+ if (*lngp != ',')
+ lngp++;
+ if (*lngp == ',')
+ lngp++;
+
+ /* latp, and lngp point to start of latitude and longitude substrings
+ // respectively.
+ */
+
+ } else if (memcmp(body, "GPGLL,", 6) == 0) {
+ /* $GPGLL,xxmm.dddd,<N|S>,yyymm.dddd,<E|W>,hhmmss.dd,S,M*hh<CR><LF> */
+ latp = body+6; // over the keyword
+ lngp = latp;
+ while (lngp < body_end && *lngp != ',') // over latitude
+ lngp++;
+ if (*lngp == ',')
+ lngp++; // and lat designator
+ if (*lngp != ',')
+ lngp++; // and lat designator
+ if (*lngp == ',')
+ lngp++;
+ /* latp, and lngp point to start of latitude and longitude substrings
+ // respectively
+ */
+ } else if (memcmp(body, "GPRMC,", 6) == 0) {
+ /* $GPRMC,hhmmss.dd,S,xxmm.dddd,<N|S>,yyymm.dddd,<E|W>,s.s,h.h,ddmmyy,d.d, <E|W>,M*hh<CR><LF>
+ // ,S, = Status: 'A' = Valid, 'V' = Invalid
+ //
+ // GPRMC,175050,A,4117.8935,N,10535.0871,W,0.0,324.3,100208,10.0,E,A*3B
+ // GPRMC,000000,V,0000.0000,0,00000.0000,0,000,000,000000,,*01/It wasn't me :)
+ // invalid..
+ // GPRMC,000043,V,4411.7761,N,07927.0448,W,0.000,0.0,290697,10.7,W*57
+ // GPRMC,003803,A,3347.1727,N,11812.7184,W,000.0,000.0,140208,013.7,E*67
+ // GPRMC,050058,A,4609.1143,N,12258.8184,W,0.000,0.0,100208,18.0,E*5B
+ */
+
+ latp = body+6; // over the keyword
+ while (latp < body_end && *latp != ',')
+ latp++; // scan over the timestamp
+ if (*latp == ',')
+ latp++; // .. and into VALIDITY
+ if (*latp != 'A' && *latp != 'V')
+ return 0; // INVALID !
+ if (*latp != ',')
+ latp++;
+ if (*latp == ',')
+ latp++;
+
+ /* now it points to latitude substring */
+ lngp = latp;
+ while (lngp < body_end && *lngp != ',')
+ lngp++;
+
+ if (*lngp == ',')
+ lngp++;
+ if (*lngp != ',')
+ lngp++;
+ if (*lngp == ',')
+ lngp++;
+
+ /* latp, and lngp point to start of latitude and longitude substrings
+ // respectively.
+ */
+
+ } else if (memcmp(body, "GPWPL,", 6) == 0) {
+ /* $GPWPL,4610.586,N,00607.754,E,4*70
+ // $GPWPL,4610.452,N,00607.759,E,5*74
+ */
+ latp = body+6;
+
+ } else if (memcmp(body, "PNTS,1,", 7) == 0) { /* PNTS version 1 */
+ /* $PNTS,1,0,11,01,2002,231932,3539.687,N,13944.480,E,0,000,5,Roppongi UID RELAY,000,1*35
+ // $PNTS,1,0,14,01,2007,131449,3535.182,N,13941.200,E,0,0.0,6,Oota-Ku KissUIDigi,000,1*1D
+ // $PNTS,1,0,17,02,2008,120824,3117.165,N,13036.481,E,49,059,1,Kagoshima,000,1*71
+ // $PNTS,1,0,17,02,2008,120948,3504.283,N,13657.933,E,00,000.0,6,,000,1*36
+ //
+ // From Alinco EJ-41U Terminal Node Controller manual:
+ //
+ // 5-4-7 $PNTS
+ // This is a private-sentence based on NMEA-0183. The data contains date,
+ // time, latitude, longitude, moving speed, direction, altitude plus a short
+ // message, group codes, and icon numbers. The EJ-41U does not analyze this
+ // format but can re-structure it.
+ // The data contains the following information:
+ // l $PNTS Starts the $PNTS sentence
+ // l version
+ // l the registered information. [0]=normal geographical location data.
+ // This is the only data EJ-41U can re-structure. [s]=Initial position
+ // for the course setting [E]=ending position for the course setting
+ // [1]=the course data between initial and ending [P]=the check point
+ // registration [A]=check data when the automatic position transmission
+ // is set OFF [R]=check data when the course data or check point data is
+ // received.
+ // l dd,mm,yyyy,hhmmss: Date and time indication.
+ // l Latitude in DMD followed by N or S
+ // l Longitude in DMD followed by E or W
+ // l Direction: Shown with the number 360 degrees divided by 64.
+ // 00 stands for true north, 16 for east. Speed in Km/h
+ // l One of 15 characters [0] to [9], [A] to [E].
+ // NTSMRK command determines this character when EJ-41U is used.
+ // l A short message up to 20 bites. Use NTSMSG command to determine this message.
+ // l A group code: 3 letters with a combination of [0] to [9], [A] to [Z].
+ // Use NTSGRP command to determine.
+ // l Status: [1] for usable information, [0] for non-usable information.
+ // l *hh<CR><LF> the check-sum and end of PNTS sentence.
+ */
+
+ if (body+55 > body_end) {
+ DEBUG_LOG("body too short");
+ return 0; /* Too short.. */
+ }
+ latp = body+7; /* Over the keyword */
+ /* Accept any registered information code */
+ if (*latp++ == ',') return 0;
+ if (*latp++ != ',') return 0;
+ /* Scan over date+time info */
+ while (*latp != ',' && latp <= body_end) ++latp;
+ if (*latp == ',') ++latp;
+ while (*latp != ',' && latp <= body_end) ++latp;
+ if (*latp == ',') ++latp;
+ while (*latp != ',' && latp <= body_end) ++latp;
+ if (*latp == ',') ++latp;
+ while (*latp != ',' && latp <= body_end) ++latp;
+ if (*latp == ',') ++latp;
+ /* now it points to latitude substring */
+ lngp = latp;
+ while (lngp < body_end && *lngp != ',')
+ lngp++;
+
+ if (*lngp == ',')
+ lngp++;
+ if (*lngp != ',')
+ lngp++;
+ if (*lngp == ',')
+ lngp++;
+
+ /* latp, and lngp point to start of latitude and longitude substrings
+ // respectively.
+ */
+#if 1
+ } else if (memcmp(body, "GPGSA,", 6) == 0 ||
+ memcmp(body, "GPVTG,", 6) == 0 ||
+ memcmp(body, "GPGSV,", 6) == 0) {
+ /* Recognized but ignored */
+ return 1;
+#endif
+ }
+
+ if (!latp || !lngp) {
+ if (debug)
+ fprintf(stderr, "Unknown NMEA: '%.11s' %.*s", pb->data, (int)(body_end - body), body);
+ return 0; /* Well.. Not NMEA frame */
+ }
+
+ // hlog(LOG_DEBUG, "NMEA parsing: %.*s", (int)(body_end - body), body);
+ // hlog(LOG_DEBUG, " lat=%.10s lng=%.10s", latp, lngp);
+
+ i = sscanf(latp, "%2d%f,%c,", &la, &lat, &lac);
+ if (i != 3)
+ return 0; // parse failure
+
+ i = sscanf(lngp, "%3d%f,%c,", &lo, &lng, &loc);
+ if (i != 3)
+ return 0; // parse failure
+
+ if (lac != 'N' && lac != 'S' && lac != 'n' && lac != 's')
+ return 0; // bad indicator value
+ if (loc != 'E' && loc != 'W' && loc != 'e' && loc != 'w')
+ return 0; // bad indicator value
+
+ // hlog(LOG_DEBUG, " lat: %c %2d %7.4f lng: %c %2d %7.4f",
+ // lac, la, lat, loc, lo, lng);
+
+ lat = (float)la + lat/60.0;
+ lng = (float)lo + lng/60.0;
+
+ if (lac == 'S' || lac == 's')
+ lat = -lat;
+ if (loc == 'W' || loc == 'w')
+ lng = -lng;
+
+ pb->packettype |= T_POSITION;
+
+ // Parse symbol from destination callsign
+ get_symbol_from_dstcall(pb, &sym_table, &sym_code);
+#ifdef DEBUG_PARSE_APRS
+ if (debug) {
+ printf("\tget_symbol_from_dstcall: %.*s => %c%c",
+ (int)(pb->dstcall_end_or_ssid - pb->srccall_end-1), pb->srccall_end+1, sym_table, sym_code);
+ }
+#endif
+
+ return pbuf_fill_pos(pb, lat, lng, sym_table, sym_code);
+}
+
+static int parse_aprs_telem(struct pbuf_t *pb, const char *body, const char *body_end)
+{
+ // float lat = 0.0, lng = 0.0;
+
+ DEBUG_LOG("parse_aprs_telem");
+
+ //pbuf_fill_pos(pb, lat, lng, 0, 0);
+ return 1; // okay
+}
+
+/*
+ * Parse a MIC-E position packet
+ *
+ * APRS PROTOCOL REFERENCE 1.0.1 Chapter 10, page 42 (52 in PDF)
+ *
+ */
+
+static int parse_aprs_mice(struct pbuf_t *pb, const unsigned char *body, const unsigned char *body_end)
+{
+ float lat = 0.0, lng = 0.0;
+ unsigned int lat_deg = 0, lat_min = 0, lat_min_frag = 0, lng_deg = 0, lng_min = 0, lng_min_frag = 0;
+ const char *d_start;
+ char dstcall[7];
+ char *p;
+ char sym_table, sym_code;
+ int posambiguity = 0;
+ int i;
+
+ DEBUG_LOG("parse_aprs_mice: %.*s", pb->packet_len-2, pb->data);
+
+ /* check packet length */
+ if (body_end - body < 8)
+ return 0;
+
+ /* check that the destination call exists and is of the right size for mic-e */
+ d_start = pb->srccall_end+1;
+ if (pb->dstcall_end_or_ssid - d_start != 6) {
+ DEBUG_LOG(".. bad destcall length! ");
+ return 0; /* eh...? */
+ }
+
+ /* validate destination call:
+ * A-K characters are not used in the last 3 characters
+ * and MNO are never used
+ */
+ if (debug)printf(" destcall='%6.6s'",d_start);
+ for (i = 0; i < 3; i++)
+ if (!((d_start[i] >= '0' && d_start[i] <= '9')
+ || (d_start[i] >= 'A' && d_start[i] <= 'L')
+ || (d_start[i] >= 'P' && d_start[i] <= 'Z'))) {
+ DEBUG_LOG(".. bad destcall characters in posits 1..3");
+ return 0;
+ }
+
+ for (i = 3; i < 6; i++)
+ if (!((d_start[i] >= '0' && d_start[i] <= '9')
+ || (d_start[i] == 'L')
+ || (d_start[i] >= 'P' && d_start[i] <= 'Z'))) {
+ DEBUG_LOG(".. bad destcall characters in posits 4..6");
+ return 0;
+ }
+
+ DEBUG_LOG("\tpassed dstcall format check");
+
+ /* validate information field (longitude, course, speed and
+ * symbol table and code are checked). Not bullet proof..
+ *
+ * 0 1 23 4 5 6 7
+ * /^[\x26-\x7f][\x26-\x61][\x1c-\x7f]{2}[\x1c-\x7d][\x1c-\x7f][\x21-\x7b\x7d][\/\\A-Z0-9]/
+ */
+ if (body[0] < 0x26 || body[0] > 0x7f) {
+ DEBUG_LOG("..bad infofield column 1");
+ return 0;
+ }
+ if (body[1] < 0x26 || body[1] > 0x61) {
+ DEBUG_LOG("..bad infofield column 2");
+ return 0;
+ }
+ if (body[2] < 0x1c || body[2] > 0x7f) {
+ DEBUG_LOG("..bad infofield column 3");
+ return 0;
+ }
+ if (body[3] < 0x1c || body[3] > 0x7f) {
+ DEBUG_LOG("..bad infofield column 4");
+ return 0;
+ }
+ if (body[4] < 0x1c || body[4] > 0x7d) {
+ DEBUG_LOG("..bad infofield column 5");
+ return 0;
+ }
+ if (body[5] < 0x1c || body[5] > 0x7f) {
+ DEBUG_LOG("..bad infofield column 6");
+ return 0;
+ }
+ if ((body[6] < 0x21 || body[6] > 0x7b)
+ && body[6] != 0x7d) {
+ DEBUG_LOG("..bad infofield column 7");
+ return 0;
+ }
+ if (!valid_sym_table_uncompressed(body[7])) {
+ DEBUG_LOG("..bad symbol table entry on column 8");
+ return 0;
+ }
+
+ DEBUG_LOG("\tpassed info format check");
+
+ /* make a local copy, we're going to modify it */
+ strncpy(dstcall, d_start, 6);
+ dstcall[6] = 0;
+
+ /* First do the destination callsign
+ * (latitude, message bits, N/S and W/E indicators and long. offset)
+ *
+ * Translate the characters to get the latitude
+ */
+
+ //fprintf(stderr, "\tuntranslated dstcall: %s\n", dstcall);
+ for (p = dstcall; *p; p++) {
+ if (*p >= 'A' && *p <= 'J')
+ *p -= 'A' - '0';
+ else if (*p >= 'P' && *p <= 'Y')
+ *p -= 'P' - '0';
+ else if (*p == 'K' || *p == 'L' || *p == 'Z')
+ *p = '_';
+ }
+ //fprintf(stderr, "\ttranslated dstcall: %s\n", dstcall);
+
+ // position ambiquity is going to get ignored now,
+ // it's not needed in this application.
+
+ if (dstcall[5] == '_') { dstcall[5] = '5'; posambiguity = 1; }
+ if (dstcall[4] == '_') { dstcall[4] = '5'; posambiguity = 2; }
+ if (dstcall[3] == '_') { dstcall[3] = '5'; posambiguity = 3; }
+ if (dstcall[2] == '_') { dstcall[2] = '3'; posambiguity = 4; }
+ if (dstcall[1] == '_' || dstcall[0] == '_') {
+ DEBUG_LOG("..bad pos-ambiguity on destcall");
+ return 0;
+ } // cannot use posamb here
+
+ // convert to degrees, minutes and decimal degrees,
+ // and then to a float lat
+
+ if (sscanf(dstcall, "%2u%2u%2u",
+ &lat_deg, &lat_min, &lat_min_frag) != 3) {
+ DEBUG_LOG("\tsscanf failed");
+ return 0;
+ }
+ lat = (float)lat_deg + (float)lat_min / 60.0 + (float)lat_min_frag / 6000.0;
+
+ // check the north/south direction and correct the latitude if necessary
+ if (d_start[3] <= 0x4c)
+ lat = 0 - lat;
+
+ /* Decode the longitude, the first three bytes of the body
+ * after the data type indicator. First longitude degrees,
+ * remember the longitude offset.
+ */
+ lng_deg = body[0] - 28;
+ if (d_start[4] >= 0x50)
+ lng_deg += 100;
+ if (lng_deg >= 180 && lng_deg <= 189)
+ lng_deg -= 80;
+ else if (lng_deg >= 190 && lng_deg <= 199)
+ lng_deg -= 190;
+
+ /* Decode the longitude minutes */
+ lng_min = body[1] - 28;
+ if (lng_min >= 60)
+ lng_min -= 60;
+
+ /* ... and minute decimals */
+ lng_min_frag = body[2] - 28;
+
+ /* apply position ambiguity to longitude */
+ switch (posambiguity) {
+ case 0:
+ /* use everything */
+ lng = (float)lng_deg + (float)lng_min / 60.0
+ + (float)lng_min_frag / 6000.0;
+ break;
+ case 1:
+ /* ignore last number of lng_min_frag */
+ lng = (float)lng_deg + (float)lng_min / 60.0
+ + (float)(lng_min_frag - lng_min_frag % 10 + 5) / 6000.0;
+ break;
+ case 2:
+ /* ignore lng_min_frag */
+ lng = (float)lng_deg + ((float)lng_min + 0.5) / 60.0;
+ break;
+ case 3:
+ /* ignore lng_min_frag and last number of lng_min */
+ lng = (float)lng_deg + (float)(lng_min - lng_min % 10 + 5) / 60.0;
+ break;
+ case 4:
+ /* minute is unused -> add 0.5 degrees to longitude */
+ lng = (float)lng_deg + 0.5;
+ break;
+ default:
+ DEBUG_LOG(".. posambiguity code BUG!");
+ return 0;
+ }
+
+ /* check the longitude E/W sign */
+ if (d_start[5] >= 0x50)
+ lng = 0 - lng;
+
+ /* save the symbol table and code */
+ sym_code = body[6];
+ sym_table = body[7];
+
+ /* ok, we're done */
+ /*
+ fprintf(stderr, "\tlat %u %u.%u (%.4f) lng %u %u.%u (%.4f)\n",
+ lat_deg, lat_min, lat_min_frag, lat,
+ lng_deg, lng_min, lng_min_frag, lng);
+ fprintf(stderr, "\tsym '%c' '%c'\n", sym_table, sym_code);
+ */
+
+ return pbuf_fill_pos(pb, lat, lng, sym_table, sym_code);
+}
+
+/*
+ * Parse a compressed APRS position packet
+ *
+ * APRS PROTOCOL REFERENCE 1.0.1 Chapter 9, page 36 (46 in PDF)
+ *
+ */
+
+static int parse_aprs_compressed(struct pbuf_t *pb, const char *body, const char *body_end)
+{
+ char sym_table, sym_code;
+ int i;
+ int lat1, lat2, lat3, lat4;
+ int lng1, lng2, lng3, lng4;
+ float lat, lng;
+
+ DEBUG_LOG("parse_aprs_compressed");
+
+ /* A compressed position is always 13 characters long.
+ * Make sure we get at least 13 characters and that they are ok.
+ * Also check the allowed base-91 characters at the same time.
+ */
+
+ if (body_end - body < 13) {
+ DEBUG_LOG("\ttoo short");
+ return 0; /* too short. */
+ }
+
+ sym_table = body[0]; /* has been validated before entering this function */
+ sym_code = body[9];
+
+ /* base-91 check */
+ for (i = 1; i <= 8; i++)
+ if (body[i] < 0x21 || body[i] > 0x7b)
+ return 0;
+
+ // fprintf(stderr, "\tpassed length and format checks, sym %c%c\n", sym_table, sym_code);
+
+ /* decode */
+ lat1 = (body[1] - 33);
+ lat2 = (body[2] - 33);
+ lat3 = (body[3] - 33);
+ lat4 = (body[4] - 33);
+
+ lat1 = ((((lat1 * 91) + lat2) * 91) + lat3) * 91 + lat4;
+
+ lng1 = (body[5] - 33);
+ lng2 = (body[6] - 33);
+ lng3 = (body[7] - 33);
+ lng4 = (body[8] - 33);
+
+ lng1 = ((((lng1 * 91) + lng2) * 91) + lng3) * 91 + lng4;
+
+ /* calculate latitude and longitude */
+
+ lat = 90.0F - ((float)(lat1) / 380926.0F);
+ lng = -180.0F + ((float)(lng1) / 190463.0F);
+
+ return pbuf_fill_pos(pb, lat, lng, sym_table, sym_code);
+}
+
+/*
+ * Parse an uncompressed "normal" APRS packet
+ *
+ * APRS PROTOCOL REFERENCE 1.0.1 Chapter 8, page 32 (42 in PDF)
+ *
+ */
+
+static int parse_aprs_uncompressed(struct pbuf_t *pb, const char *body, const char *body_end)
+{
+ char posbuf[20];
+ unsigned int lat_deg = 0, lat_min = 0, lat_min_frag = 0, lng_deg = 0, lng_min = 0, lng_min_frag = 0;
+ float lat, lng;
+ char lat_hemi, lng_hemi;
+ char sym_table, sym_code;
+ int issouth = 0;
+ int iswest = 0;
+
+ DEBUG_LOG("parse_aprs_uncompressed");
+
+ if (body_end - body < 19) {
+ DEBUG_LOG("\ttoo short");
+ return 0;
+ }
+
+ /* make a local copy, so we can overwrite it at will. */
+ memcpy(posbuf, body, 19);
+ posbuf[19] = 0;
+ // fprintf(stderr, "\tposbuf: %s\n", posbuf);
+
+ // position ambiquity is going to get ignored now,
+ // it's not needed in this application.
+
+ /* lat */
+ if (posbuf[2] == ' ') posbuf[2] = '3';
+ if (posbuf[3] == ' ') posbuf[3] = '5';
+ if (posbuf[5] == ' ') posbuf[5] = '5';
+ if (posbuf[6] == ' ') posbuf[6] = '5';
+ /* lng */
+ if (posbuf[12] == ' ') posbuf[12] = '3';
+ if (posbuf[13] == ' ') posbuf[13] = '5';
+ if (posbuf[15] == ' ') posbuf[15] = '5';
+ if (posbuf[16] == ' ') posbuf[16] = '5';
+
+ // fprintf(stderr, "\tafter filling amb: %s\n", posbuf);
+ /* 3210.70N/13132.15E# */
+ if (sscanf(posbuf, "%2u%2u.%2u%c%c%3u%2u.%2u%c%c",
+ &lat_deg, &lat_min, &lat_min_frag, &lat_hemi, &sym_table,
+ &lng_deg, &lng_min, &lng_min_frag, &lng_hemi, &sym_code) != 10) {
+ DEBUG_LOG("\tsscanf failed");
+ return 0;
+ }
+
+ if (!valid_sym_table_uncompressed(sym_table))
+ sym_table = 0;
+
+ if (lat_hemi == 'S' || lat_hemi == 's')
+ issouth = 1;
+ else if (lat_hemi != 'N' && lat_hemi != 'n')
+ return 0; /* neither north or south? bail out... */
+
+ if (lng_hemi == 'W' || lng_hemi == 'w')
+ iswest = 1;
+ else if (lng_hemi != 'E' && lng_hemi != 'e')
+ return 0; /* neither west or east? bail out ... */
+
+ if (lat_deg > 89 || lng_deg > 179)
+ return 0; /* too large values for lat/lng degrees */
+
+ lat = (float)lat_deg + (float)lat_min / 60.0 + (float)lat_min_frag / 6000.0;
+ lng = (float)lng_deg + (float)lng_min / 60.0 + (float)lng_min_frag / 6000.0;
+
+ /* Finally apply south/west indicators */
+ if (issouth)
+ lat = 0.0 - lat;
+ if (iswest)
+ lng = 0.0 - lng;
+
+ // fprintf(stderr, "\tlat %u %u.%u %c (%.3f) lng %u %u.%u %c (%.3f)\n",
+ // lat_deg, lat_min, lat_min_frag, (int)lat_hemi, lat,
+ // lng_deg, lng_min, lng_min_frag, (int)lng_hemi, lng);
+ // fprintf(stderr, "\tsym '%c' '%c'\n", sym_table, sym_code);
+
+ return pbuf_fill_pos(pb, lat, lng, sym_table, sym_code);
+}
+
+/*
+ * Parse an APRS object
+ *
+ * APRS PROTOCOL REFERENCE 1.0.1 Chapter 11, page 58 (68 in PDF)
+ *
+ */
+
+static int parse_aprs_object(struct pbuf_t *pb, const char *body, const char *body_end)
+{
+ int i;
+ int namelen = -1;
+
+ pb->packettype |= T_OBJECT;
+
+ DEBUG_LOG("parse_aprs_object");
+
+ /* check that the object name ends with either * or _ */
+ if (*(body + 9) != '*' && *(body + 9) != '_') {
+ DEBUG_LOG("\tinvalid object kill character");
+ return 0;
+ }
+
+ /* check that the timestamp ends with one of the valid timestamp type IDs */
+ char tz_end = body[16];
+ if (tz_end != 'z' && tz_end != 'h' && tz_end != '/') {
+ DEBUG_LOG("\tinvalid object timestamp character: '%c'", tz_end);
+ return 0;
+ }
+
+ /* check object's name - scan for non-printable characters and the last
+ * non-space character
+ */
+ for (i = 0; i < 9; i++) {
+ if (body[i] < 0x20 || body[i] > 0x7e) {
+ DEBUG_LOG("\tobject name has unprintable characters");
+ return 0; // non-printable
+ }
+ if (body[i] != ' ')
+ namelen = i;
+ }
+
+ if (namelen < 0) {
+ DEBUG_LOG("\tobject has empty name");
+ return 0;
+ }
+
+ pb->srcname = body;
+ pb->srcname_len = namelen+1;
+
+ DEBUG_LOG("object name: '%.*s'\n", pb->srcname_len, pb->srcname);
+
+ /* Forward the location parsing onwards */
+ if (valid_sym_table_compressed(body[17]))
+ return parse_aprs_compressed(pb, body + 17, body_end);
+
+ if (body[17] >= '0' && body[17] <= '9')
+ return parse_aprs_uncompressed(pb, body + 17, body_end);
+
+ DEBUG_LOG("no valid position in object");
+
+ return 0;
+}
+
+/*
+ * Parse an APRS item
+ *
+ * APRS PROTOCOL REFERENCE 1.0.1 Chapter 11, page 59 (69 in PDF)
+ *
+ */
+
+static int parse_aprs_item(struct pbuf_t *pb, const char *body, const char *body_end)
+{
+ int i;
+
+ pb->packettype |= T_ITEM;
+
+ DEBUG_LOG("parse_aprs_item");
+
+ /* check item's name - scan for non-printable characters and the
+ * ending character ! or _
+ */
+ for (i = 0; i < 9 && body[i] != '!' && body[i] != '_'; i++) {
+ if (body[i] < 0x20 || body[i] > 0x7e) {
+ DEBUG_LOG("\titem name has unprintable characters");
+ return 0; /* non-printable */
+ }
+ }
+
+ if (body[i] != '!' && body[i] != '_') {
+ DEBUG_LOG("\titem name ends with neither ! or _");
+ return 0;
+ }
+
+ if (i < 3 || i > 9) {
+ DEBUG_LOG("\titem name has invalid length");
+ return 0;
+ }
+
+ pb->srcname = body;
+ pb->srcname_len = i;
+
+ //fprintf(stderr, "\titem name: '%.*s'\n", pb->srcname_len, pb->srcname);
+
+ /* Forward the location parsing onwards */
+ i++;
+ if (valid_sym_table_compressed(body[i]))
+ return parse_aprs_compressed(pb, body + i, body_end);
+
+ if (body[i] >= '0' && body[i] <= '9')
+ return parse_aprs_uncompressed(pb, body + i, body_end);
+
+ DEBUG_LOG("\tno valid position in item");
+
+ return 0;
+}
+
+
+#if 0
+int parse_aprs_txgate(struct pbuf_t *pb, int look_inside_3rd_party, historydb_t *historydb)
+{
+ int rc = parse_aprs(pb, look_inside_3rd_party, historydb);
+
+ if (pb->packettype & T_THIRDPARTY) {
+ // Tx-IGate needs to know from RF received frames, if there is
+ // source address that arrived from an Tx-IGate...
+
+ const char *body;
+ const char *body_end;
+ const char *pos_start;
+ const char *info_start = pb->info_start;
+
+
+
+ }
+ return rc;
+}
+#endif
+
+/*
+ * Try to parse an APRS packet.
+ * Returns 1 if position was parsed successfully,
+ * 0 if parsing failed.
+ *
+ * Does also front-end part of the output filter's
+ * packet type classification job.
+ *
+ * TODO: Recognize TELEM packets in !/=@ packets too!
+ *
+ * Return 0 for parse failures, 1 for OK.
+ */
+
+int parse_aprs(struct pbuf_t*const pb, historydb_t*const historydb)
+{
+ char packettype, poschar;
+ int paclen;
+ int rc;
+ const char *body;
+ const char *body_end;
+ const char *pos_start;
+ const char *info_start = pb->info_start;
+
+ int look_inside_3rd_party = 1; // Look there once..
+
+ pb->packettype = T_ALL;
+ pb->flags = 0;
+
+ if (!pb->info_start)
+ return 0;
+
+ if (pb->data[0] == 'C' && /* Perhaps CWOP ? */
+ pb->data[1] == 'W') {
+ const char *s = pb->data + 2;
+ const char *pe = pb->data + pb->packet_len;
+ for ( ; *s && s < pe ; ++s ) {
+ int c = *s;
+ if (c < '0' || c > '9')
+ break;
+ }
+ if (*s == '>')
+ pb->packettype |= T_CWOP;
+ }
+
+ /* the following parsing logic has been translated from Ham::APRS::FAP
+ * Perl module to C
+ */
+
+ // ignore the CRLF in the end of the body
+ body_end = pb->data + pb->packet_len; // NOTE! Difference from original aprsc code
+
+ do {
+ // body is right after the packet type character
+ body = info_start + 1;
+
+ // length of the info field:
+ paclen = body_end - info_start;
+
+ if (paclen < 1) return 0; // consumed all, or empty packet
+
+ // Check the first character of the packet and
+ // determine the packet type
+ packettype = *info_start;
+
+ // Exit this loop unless it is 3rd-party frame
+ if (packettype != '}') break;
+
+ // Look for ':' character separating address block
+ // from 3rd-party body
+ info_start = memchr(body, ':', (int)(body_end - body));
+ if (info_start == NULL) {
+ // Not valid 3rd party frame!
+ return 0;
+ }
+ pb->packettype |= T_THIRDPARTY;
+ if (!look_inside_3rd_party)
+ return 1; // Correct 3rd-party, don't look further.
+
+ // Look once inside the 3rd party frame,
+ // this is used in aprx's tx-igate, which builds
+ // the 3rd-party frame before parsing message-to-be-tx:ed
+ // .. and doing content filters.
+ --look_inside_3rd_party;
+ pb->packettype = 0;
+
+ // Skip over the ':'
+ ++info_start;
+ continue; // and loop..
+
+ } while (1);
+
+ switch (packettype) {
+ /* the following are obsolete mic-e types: 0x1c 0x1d
+ * case 0x1c:
+ * case 0x1d:
+ */
+ case 0x27: /* ' */
+ case 0x60: /* ` */
+ /* could be mic-e, minimum body length 9 chars */
+ if (paclen >= 9) {
+ pb->packettype |= T_POSITION;
+ rc = parse_aprs_mice(pb,
+ (const unsigned char*)body,
+ (const unsigned char*)body_end);
+ DEBUG_LOG("\n");
+ return rc;
+ }
+ return 0; // bad
+
+ case '!':
+ if (*body == '!') { /* Ultimeter 2000 - "tnc2addr:!!" */
+ pb->packettype |= T_WX;
+ return 1; // Known Ultimeter format
+ }
+ case '=':
+ case '/':
+ case '@':
+ /* check that we won't run over right away */
+ if (body_end - body < 10)
+ return 0; // bad
+ /* Normal or compressed location packet, with or without
+ * timestamp, with or without messaging capability
+ *
+ * ! and / have messaging, / and @ have a prepended timestamp
+ */
+ pb->packettype |= T_POSITION;
+ if (packettype == '/' || packettype == '@') {
+ /* With a prepended timestamp, jump over it. */
+ body += 7;
+ }
+ poschar = *body;
+ if (valid_sym_table_compressed(poschar)) { /* [\/\\A-Za-j] */
+ /* compressed position packet */
+ rc = 0;
+ if (body_end - body >= 13)
+ rc = parse_aprs_compressed(pb, body, body_end);
+ DEBUG_LOG("\n");
+ return rc;
+
+ } else if (poschar >= 0x30 && poschar <= 0x39) { /* [0-9] */
+ /* normal uncompressed position */
+ rc = 0;
+ if (body_end - body >= 19)
+ rc = parse_aprs_uncompressed(pb, body, body_end);
+ DEBUG_LOG("\n");
+ return rc;
+ }
+ return 0;
+
+ case '$':
+ if (body_end - body > 10) {
+ // Is it OK to declare it as position packet ?
+ rc = parse_aprs_nmea(pb, body, body_end);
+ DEBUG_LOG("\n");
+ return rc;
+ }
+ return 0;
+
+ case ':':
+ pb->packettype |= T_MESSAGE;
+ // quick and loose way to identify NWS and SKYWARN messages
+ // they do apparently originate from "WXSRV", but that is not
+ // guaranteed thing...
+ if (memcmp(body,"NWS-",4) == 0) // as seen on specification
+ pb->packettype |= T_NWS;
+ if (memcmp(body,"NWS_",4) == 0) // as seen on data
+ pb->packettype |= T_NWS;
+ if (memcmp(body,"SKY",3) == 0) // as seen on specification
+ pb->packettype |= T_NWS;
+
+ // Is it perhaps TELEMETRY related "message" ?
+ if ( body[9] == ':' &&
+ ( memcmp( body+9, ":PARM.", 6 ) == 0 ||
+ memcmp( body+9, ":UNIT.", 6 ) == 0 ||
+ memcmp( body+9, ":EQNS.", 6 ) == 0 ||
+ memcmp( body+9, ":BITS.", 6 ) == 0 )) {
+ pb->packettype &= ~T_MESSAGE;
+ pb->packettype |= T_TELEMETRY;
+ // Fall through to recipient location lookup
+ }
+
+ // Or perhaps a DIRECTED QUERY ?
+ if (body[9] == ':' && body[10] == '?') {
+ pb->packettype &= ~T_MESSAGE;
+ pb->packettype |= T_QUERY;
+ // Fall through to recipient location lookup
+ }
+
+ // Now find out if the message RECIPIENT address is known
+ // to have some location data ? Because then we can treat
+ // them the same way in filters as we do those with real
+ // positions..
+ {
+ const char *p;
+ int i;
+#ifndef DISABLE_IGATE
+ history_cell_t *history;
+#endif
+ pb->dstname = body;
+ p = body;
+ for (i = 0; i < CALLSIGNLEN_MAX; ++i) {
+ // the recipient address is space padded
+ // to 9 chars, while our historydb is not.
+ if (*p == 0 || *p == ' ' || *p == ':')
+ break;
+ }
+ pb->dstname_len = p - body;
+#ifndef DISABLE_IGATE
+ if (historydb != NULL) {
+ history = historydb_lookup( historydb, pb->dstname, i );
+ if (history != NULL) {
+ pb->lat = history->lat;
+ pb->lng = history->lon;
+ pb->cos_lat = history->coslat;
+
+ pb->flags |= F_HASPOS;
+ }
+ }
+#endif
+ }
+ return 1;
+
+ case ';':
+ if (body_end - body > 29) {
+ rc = parse_aprs_object(pb, body, body_end);
+ DEBUG_LOG("\n");
+ return rc;
+ }
+ return 0; // too short
+
+ case '>':
+ pb->packettype |= T_STATUS;
+ return 1; // ok
+
+ case '<':
+ pb->packettype |= T_STATCAPA;
+ return 1; // ok
+
+ case '?':
+ pb->packettype |= T_QUERY;
+ return 1; // ok at igate/digi
+
+ case ')':
+ if (body_end - body > 18) {
+ rc = parse_aprs_item(pb, body, body_end);
+ DEBUG_LOG("\n");
+ return rc;
+ }
+ return 0; // too short
+
+
+ case 'T':
+ if (body_end - body > 18) {
+ pb->packettype |= T_TELEMETRY;
+ rc = parse_aprs_telem(pb, body, body_end);
+ DEBUG_LOG("\n");
+ return rc;
+ }
+ return 0; // too short
+
+ case '#': /* Peet Bros U-II Weather Station */
+ case '*': /* Peet Bros U-I Weather Station */
+ case '_': /* Weather report without position */
+ pb->packettype |= T_WX;
+ return 1; // good
+
+ case '{':
+ pb->packettype |= T_USERDEF;
+ return 1; // okay at digi?
+
+ // the packettype is never '}'
+ // case '}':
+ // pb->packettype |= T_THIRDPARTY;
+ // return 1; // 3rd-party is okay at digi
+
+ default:
+ break;
+ }
+
+ /* When all else fails, try to look for a !-position that can
+ * occur anywhere within the 40 first characters according
+ * to the spec. (X1J TNC digipeater bugs...)
+ */
+ pos_start = memchr(body, '!', body_end - body);
+ if ((pos_start) && pos_start - body <= 39) {
+ poschar = *pos_start;
+ if (valid_sym_table_compressed(poschar)) { /* [\/\\A-Za-j] */
+ /* compressed position packet */
+ int rc = 0;
+ if (body_end - pos_start >= 13)
+ rc = parse_aprs_compressed(pb, pos_start, body_end);
+ DEBUG_LOG("\n");
+ return rc;
+ } else if (poschar >= 0x30 && poschar <= 0x39) { /* [0-9] */
+ /* normal uncompressed position */
+ int rc = 0;
+ if (body_end - pos_start >= 19)
+ rc = parse_aprs_uncompressed(pb, pos_start, body_end);
+ DEBUG_LOG("\n");
+ return rc;
+ }
+ }
+
+ return 0; // bad
+}
+
+/*
+ * Parse an aprs text message (optional, only done to messages addressed to
+ * SERVER
+ */
+
+int parse_aprs_message(const struct pbuf_t *pb, struct aprs_message_t * const am)
+{
+ const char *p;
+
+ memset(am, 0, sizeof(*am));
+
+ if (!(pb->packettype & T_MESSAGE))
+ return -1;
+
+ if (pb->info_start[10] != ':')
+ return -2;
+
+ am->body = pb->info_start + 11;
+ /* -2 for the CRLF already in place */
+ am->body_len = pb->packet_len - 2 - (pb->info_start - pb->data);
+
+ /* search for { looking backwards from the end of the packet,
+ * it separates the msgid
+ */
+ p = am->body + am->body_len - 1;
+ while (p > am->body && *p != '{')
+ p--;
+
+ if (*p == '{') {
+ am->msgid = p+1;
+ am->msgid_len = pb->packet_len - 2 - (am->msgid - pb->data);
+ am->body_len = p - am->body;
+ }
+
+ /* check if this is an ACK */
+ if ((!am->msgid_len) && am->body_len > 3
+ && am->body[0] == 'a' && am->body[1] == 'c' && am->body[2] == 'k') {
+ am->is_ack = 1;
+ am->msgid = am->body + 3;
+ am->msgid_len = am->body_len - 3;
+ am->body_len = 0;
+ return 0;
+ }
+
+ /* check if this is an REJ */
+ if ((!am->msgid_len) && am->body_len > 3
+ && am->body[0] == 'r' && am->body[1] == 'e' && am->body[2] == 'j') {
+ am->is_rej = 1;
+ am->msgid = am->body + 3;
+ am->msgid_len = am->body_len - 3;
+ am->body_len = 0;
+ return 0;
+ }
+
+ return 0;
+}
diff --git a/pbuf.c b/pbuf.c
new file mode 100644
index 0000000..baee971
--- /dev/null
+++ b/pbuf.c
@@ -0,0 +1,237 @@
+/* **************************************************************** *
+ * *
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ * **************************************************************** */
+
+#define _SVID_SOURCE 1
+
+#include "aprx.h"
+
+/*
+ * - Allocate pbuf
+ * - Free pbuf
+ * - Handle refcount (get/put)
+ */
+
+#ifndef _FOR_VALGRIND_
+static cellarena_t *pbuf_cells;
+#endif
+
+// int pbuf_size = sizeof(struct pbuf_t); // 152 bytes on i386
+// int pbuf_alignment = __alignof__(struct pbuf_t); // 8 on i386
+
+// 2150 byte pbuf takes in an AX.25 packet of about 1kB in size,
+// and in APRS use there never should be larger than about 512 bytes.
+// A 16 kB arena fits in 7 of these humongous pbufs.
+
+const int pbufcell_size = sizeof(struct pbuf_t) + 2150;
+const int pbufcell_align = __alignof__(struct pbuf_t);
+
+void pbuf_init(void)
+{
+#ifndef _FOR_VALGRIND_
+ /* A _few_... */
+
+ pbuf_cells = cellinit( "filter",
+ pbufcell_size,
+ pbufcell_align,
+ CELLMALLOC_POLICY_LIFO,
+ 16, // 16 kB at the time
+ 0 // minfree
+ );
+#endif
+}
+
+static void pbuf_free(struct pbuf_t *pb)
+{
+#ifndef _FOR_VALGRIND_
+ cellfree(pbuf_cells, pb);
+#else
+ free(pb);
+#endif
+ if (debug > 1) printf("pbuf_free(%p)\n",pb);
+}
+
+static struct pbuf_t *pbuf_alloc( const int axlen,
+ const int tnc2len )
+{
+ int pblen = sizeof(struct pbuf_t) + axlen + tnc2len + 2;
+
+#ifndef _FOR_VALGRIND_
+ // Picks suitably sized pbuf, and pre-cleans it
+ // before passing to user
+
+ struct pbuf_t *pb;
+ if (pblen > 2150) {
+ // Outch!
+ return NULL;
+ }
+ pb = cellmalloc(pbuf_cells);
+ memset(pb, 0, pblen );
+#else
+ // No size limits with valgrind..
+ struct pbuf_t *pb = calloc( 1, pblen );
+#endif
+
+ if (debug > 1) printf("pbuf_alloc(%d,%d) -> %p\n",axlen,tnc2len,pb);
+
+ pb->packet_len = tnc2len;
+ pb->buf_len = tnc2len;
+ pb->data[tnc2len] = 0;
+ pb->ax25addr = (uint8_t*)pb->data + tnc2len + 1;
+
+
+ return pb;
+}
+
+struct pbuf_t *pbuf_get( struct pbuf_t *pb )
+{
+ // Increments refcount
+ pb->refcount += 1;
+ return pb;
+}
+
+void pbuf_put( struct pbuf_t *pb )
+{
+ // Decrements refcount, if 0 -> free()!
+ pb->refcount -= 1;
+ if (pb->refcount == 0)
+ pbuf_free(pb);
+}
+
+
+static struct pbuf_t *_pbuf_new(const int is_aprs, const int digi_like_aprs, const int axlen, const int tnc2len);
+static struct pbuf_t *_pbuf_new(const int is_aprs, const int digi_like_aprs, const int axlen, const int tnc2len)
+{
+ struct pbuf_t *pb = pbuf_alloc( axlen, tnc2len );
+ if (pb == NULL) return NULL;
+
+ pbuf_get(pb);
+
+ pb->is_aprs = is_aprs;
+ pb->digi_like_aprs = digi_like_aprs;
+ pb->t = tick.tv_sec; // Arrival time
+
+ return pb;
+}
+
+
+// Do the pbuf filling in single location, processes the TNC2 header data
+struct pbuf_t * pbuf_new( const int is_aprs, const int digi_like_aprs,
+ const int tnc2addrlen, const char *tnc2buf, const int tnc2len,
+ const int ax25addrlen, const void *ax25buf, const int ax25len )
+{
+ char *p;
+
+ char *src_end; /* pointer to the > after srccall */
+ char *path_start; /* pointer to the start of the path */
+ const char *path_end; /* pointer to the : after the path */
+ const char *packet_end; /* pointer to the end of the packet */
+ const char *info_start; /* pointer to the beginning of the info */
+ const char *info_end; /* end of the info */
+ char *dstcall_end_or_ssid; /* end of dstcall, before SSID ([-:,]) */
+ char *dstcall_end; /* end of dstcall including SSID ([:,]) */
+ char *via_start; /* start of the digipeater path (after dstcall,) */
+ // const char *data; /* points to original incoming path/payload separating ':' character */
+ // int datalen; /* length of the data block excluding tail \r\n */
+ int pathlen; /* length of the path == data-s */
+ struct pbuf_t *pb;
+
+ /* a packet looks like:
+ * SRCCALL>DSTCALL,PATH,PATH:INFO\r\n
+ * (we have normalized the \r\n by now)
+ *
+ * The tnc2addrlen is index of the first ':'.
+ */
+
+ path_end = tnc2buf + tnc2addrlen;
+ pathlen = tnc2addrlen;
+ // data = path_end; // Begins with ":"
+ // datalen = tnc2len - pathlen; // Not including line end \r\n
+
+ packet_end = tnc2buf + tnc2len; // Just to compare against far end..
+
+ /* look for the '>' */
+ src_end = memchr(tnc2buf, '>', pathlen < CALLSIGNLEN_MAX+1 ? pathlen : CALLSIGNLEN_MAX+1);
+ if (!src_end) {
+ return NULL; // No ">" in packet start..
+ }
+
+ path_start = src_end+1;
+ if (path_start >= packet_end) { // We're already at the path end
+ return NULL;
+ }
+
+ if (src_end - tnc2buf > CALLSIGNLEN_MAX || src_end - tnc2buf < CALLSIGNLEN_MIN) {
+ return NULL; /* too long source callsign */
+ }
+
+ info_start = path_end+1; // @":"+1 - first char of the payload
+ if (info_start >= packet_end) {
+ return NULL;
+ }
+
+ /* see that there is at least some data in the packet */
+ info_end = packet_end;
+ if (info_end <= info_start) {
+ return NULL;
+ }
+
+ /* look up end of dstcall (excluding SSID - this is the way dupecheck and
+ * mic-e parser wants it)
+ */
+
+ dstcall_end = path_start;
+ while (dstcall_end < path_end && *dstcall_end != '-' && *dstcall_end != ',' && *dstcall_end != ':')
+ dstcall_end++;
+ dstcall_end_or_ssid = dstcall_end; // OK, SSID is here (or the dstcall end), go for the real end
+ while (dstcall_end < path_end && *dstcall_end != ',' && *dstcall_end != ':')
+ dstcall_end++;
+
+ if (dstcall_end - path_start > CALLSIGNLEN_MAX) {
+ return NULL; /* too long for destination callsign */
+ }
+
+ /* where does the digipeater path start? */
+ via_start = dstcall_end;
+ while (via_start < path_end && (*via_start != ',' && *via_start != ':')) {
+ via_start++;
+ }
+
+ pb = _pbuf_new( is_aprs, digi_like_aprs, ax25len, tnc2len );
+ if (!pb) {
+ // This should never happen...
+ return NULL;
+ }
+
+ // copy TNC2 data to its area
+ p = pb->data;
+ memcpy(p, tnc2buf, tnc2len);
+ p += tnc2len;
+
+ // Copy AX.25 data to its area..
+ memcpy(pb->ax25addr, ax25buf, ax25len);
+ pb->ax25addrlen = ax25addrlen;
+ pb->ax25data = pb->ax25addr + ax25addrlen;
+ pb->ax25datalen = ax25len - ax25addrlen;
+
+ // How much there really is data?
+ pb->packet_len = tnc2len;
+
+ packet_end = p; /* for easier overflow checking expressions */
+ /* fill necessary info for parsing and dupe checking in the packet buffer */
+ pb->srcname = pb->data;
+ pb->srcname_len = src_end - tnc2buf;
+ pb->srccall_end = pb->data + (src_end - tnc2buf); // "srccall>.." <-- @'>'
+ pb->dstcall_end_or_ssid = pb->data + (dstcall_end_or_ssid - tnc2buf);
+ pb->dstcall_end = pb->data + (dstcall_end - tnc2buf);
+ pb->dstcall_len = via_start - src_end - 1;
+ pb->info_start = pb->data + tnc2addrlen + 1;
+
+ return pb;
+}
diff --git a/pbuf.h b/pbuf.h
new file mode 100644
index 0000000..d386910
--- /dev/null
+++ b/pbuf.h
@@ -0,0 +1,125 @@
+/*
+ * aprsc
+ *
+ * (c) Heikki Hannikainen, OH7LZB <hessu at hes.iki.fi>
+ *
+ * This program is licensed under the BSD license, which can be found
+ * in the file LICENSE.
+ *
+ */
+
+/* Modified for APRX by Matti Aarnio, OH2MQK
+ * Altered name from worker.h to pbuf.h, and
+ * dropped about 70% of worker.h stuff...
+ */
+
+#ifndef PBUF_H
+#define PBUF_H
+
+/* minimum and maximum length of a callsign on APRS-IS */
+#define CALLSIGNLEN_MIN 3
+#define CALLSIGNLEN_MAX 9
+
+/* packet length limiters and buffer sizes */
+#define PACKETLEN_MIN 10 /* minimum length for a valid APRS-IS packet: "A1A>B1B:\r\n" */
+#define PACKETLEN_MAX 512 /* maximum length for a valid APRS-IS packet (incl. CRLF) */
+
+/*
+ * Packet length statistics:
+ *
+ * <= 80: about 25%
+ * <= 90: about 36%
+ * <= 100: about 73%
+ * <= 110: about 89%
+ * <= 120: about 94%
+ * <= 130: about 97%
+ * <= 140: about 98.7%
+ * <= 150: about 99.4%
+ */
+
+#define PACKETLEN_MAX_SMALL 100
+#define PACKETLEN_MAX_MEDIUM 180 /* about 99.5% are smaller than this */
+#define PACKETLEN_MAX_LARGE PACKETLEN_MAX
+
+/* number of pbuf_t structures to allocate at a time */
+#define PBUF_ALLOCATE_BUNCH_SMALL 2000 /* grow to 2000 in production use */
+#define PBUF_ALLOCATE_BUNCH_MEDIUM 2000 /* grow to 2000 in production use */
+#define PBUF_ALLOCATE_BUNCH_LARGE 50 /* grow to 50 in production use */
+
+/* a packet buffer */
+/* Type flags -- some can happen in combinations: T_CWOP + T_WX / T_CWOP + T_POSITION ... */
+#define T_POSITION (1 << 0) // Packet is of position type
+#define T_OBJECT (1 << 1) // packet is an object
+#define T_ITEM (1 << 2) // packet is an item
+#define T_MESSAGE (1 << 3) // packet is a message
+#define T_NWS (1 << 4) // packet is a NWS message
+#define T_WX (1 << 5) // packet is WX data
+#define T_TELEMETRY (1 << 6) // packet is telemetry
+#define T_QUERY (1 << 7) // packet is a query
+#define T_STATUS (1 << 8) // packet is status
+#define T_USERDEF (1 << 9) // packet is userdefined
+#define T_CWOP (1 << 10) // packet is recognized as CWOP
+#define T_STATCAPA (1 << 11) // packet is station capability response
+#define T_THIRDPARTY (1 << 12)
+#define T_ALL (1 << 15) // set on _all_ packets
+
+#define F_DUPE (1 << 0) // Duplicate of a previously seen packet
+#define F_HASPOS (1 << 1) // This packet has valid parsed position
+#define F_HAS_TCPIP (1 << 2) // There is a TCPIP* in the path
+
+struct pbuf_t {
+ struct pbuf_t *next;
+
+ int16_t is_aprs; // If not, then just digipeated frame..
+ int16_t digi_like_aprs;
+ int16_t source_if_group;
+
+ int16_t refcount;
+
+ int16_t reqcount; // How many digipeat hops are requested?
+ int16_t donecount; // How many digipeat hops are already done?
+
+ time_t t; /* when the packet was received */
+ uint32_t seqnum; /* ever increasing counter, dupecheck sets */
+ uint16_t packettype; /* bitmask: one or more of T_* */
+ uint16_t flags; /* bitmask: one or more of F_* */
+ uint16_t srcname_len; /* parsed length of source (object, item, srcall) name 3..9 */
+ uint16_t dstcall_len; /* parsed length of destination callsign *including* SSID */
+ uint16_t dstname_len; /* parsed length of message destination including SSID */
+ uint16_t entrycall_len;
+
+ int packet_len; /* the actual length of the TNC2 packet */
+ int buf_len; /* the length of this buffer */
+
+ const char *srccall_end; /* source callsign with SSID */
+ const char *dstcall_end_or_ssid; /* end of dest callsign (without SSID) */
+ const char *dstcall_end; /* end of dest callsign with SSID */
+ const char *qconst_start; /* "qAX,incomingSSID:" -- for q and e filters */
+ const char *info_start; /* pointer to start of info field */
+ const char *srcname; /* source's name (either srccall or object/item name) */
+ const char *dstname; /* message destination callsign */
+
+ float lat; /* if the packet is PT_POSITION, latitude and longitude go here */
+ float lng; /* .. in RADIAN */
+ float cos_lat; /* cache of COS of LATitude for radial distance filter */
+
+ char symbol[3]; /* 2(+1) chars of symbol, if any, NUL for not found */
+
+ uint8_t *ax25addr; // Start of AX.25 address
+ int ax25addrlen; // length of AX.25 address
+
+ uint8_t *ax25data; // Start of AX.25 data after addresses
+ int ax25datalen; // length of that data
+
+ char data[1];
+};
+
+/* global packet buffer */
+extern struct pbuf_t *pbuf_global;
+extern struct pbuf_t *pbuf_global_last;
+extern struct pbuf_t **pbuf_global_prevp;
+extern struct pbuf_t *pbuf_global_dupe;
+extern struct pbuf_t *pbuf_global_dupe_last;
+extern struct pbuf_t **pbuf_global_dupe_prevp;
+
+#endif
diff --git a/rpm/aprx.default b/rpm/aprx.default
new file mode 100644
index 0000000..8fe2c08
--- /dev/null
+++ b/rpm/aprx.default
@@ -0,0 +1,10 @@
+#
+# STARTAPRX: start aprx on boot. Should be set to "yes" once you have
+# configured aprx.
+#
+STARTAPRX="no"
+
+#
+# Additional options that are passed to the Daemon.
+#
+DAEMON_OPTS=""
diff --git a/rpm/aprx.init b/rpm/aprx.init
new file mode 100755
index 0000000..4ac316c
--- /dev/null
+++ b/rpm/aprx.init
@@ -0,0 +1,84 @@
+#!/bin/bash
+#
+# chkconfig: - 16 84
+# description: Start up a receive only APRS igate
+# processname: aprx
+# config: /etc/sysconfig/aprx
+
+### BEGIN INIT INFO -- debian style:
+# Provides: aprx
+# Required-Start: $syslog $local_fs
+# Required-Stop: $syslog $local_fs
+# Default-Stop: 0 1 6
+# Short-Description: start and stop aprx
+# Description: Monitor and gateway radio amateur APRS radio network datagrams
+### END INIT INFO
+
+# source function library
+. /etc/rc.d/init.d/functions
+
+# Check that networking is up.
+[ "${NETWORKING}" = "no" ] && exit 0
+
+# defaults
+
+PATH=/sbin:/bin:/usr/sbin:/usr/bin
+DAEMON=/usr/sbin/aprx
+NAME=aprx
+DESC="aprx igate"
+
+# source the config, if it exists
+if [ -f /etc/sysconfig/aprx ] ; then
+ . /etc/sysconfig/aprx
+fi
+
+test -x $DAEMON || exit 0
+
+if [ "$STARTAPRX" != "yes" ];then
+ echo "Starting of $NAME not enabled in /etc/sysconfig/$NAME."
+ exit 0
+fi
+
+set -e
+
+
+case "$1" in
+ start)
+ echo -n $"Starting aprx server: "
+ $DAEMON
+ disown -ar
+ usleep 500000
+ status $NAME &> /dev/null && echo_success || echo_failure
+ RETVAL=$?
+ [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$NAME
+ echo
+ ;;
+ stop)
+ echo -n $"Shutting down aprx server: "
+ killproc $DAEMON
+ RETVAL=$?
+ [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$NAME
+ echo_success
+ ;;
+ restart|reload)
+ $0 stop
+ $0 start
+ RETVAL=$?
+ ;;
+ condrestart)
+ if [ -f /var/lock/subsys/$NAME ]; then
+ $0 stop
+ $0 start
+ fi
+ RETVAL=$?
+ ;;
+ status)
+ status $NAME
+ RETVAL=$?
+ ;;
+ *)
+ echo $"Usage: $0 {start|stop|restart|reload|condrestart|status}"
+ exit 1
+esac
+
+exit $RETVAL
diff --git a/rpm/aprx.service b/rpm/aprx.service
new file mode 100644
index 0000000..dee7b9b
--- /dev/null
+++ b/rpm/aprx.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=Amateur Radio APRS Gateway & Digipeater
+Documentation=man:aprx(8)
+
+[Service]
+Type=simple
+ExecStart=/usr/sbin/aprx
+# doesn't do any internal reload
+
+[Install]
+WantedBy=multi-user.target
diff --git a/ssl.c b/ssl.c
new file mode 100644
index 0000000..3fa2eaa
--- /dev/null
+++ b/ssl.c
@@ -0,0 +1,920 @@
+
+/*
+ * This OpenSSL interface code has been proudly copied from
+ * the excellent NGINX web server.
+ *
+ * Its license is reproduced here.
+ */
+
+/*
+ * Copyright (C) 2002-2013 Igor Sysoev
+ * Copyright (C) 2011-2013 Nginx, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * OpenSSL thread-safe example code (ssl_thread_* functions) have been
+ * proudly copied from the excellent CURL package, the original author
+ * is Jeremy Brown.
+ *
+ * https://github.com/bagder/curl/blob/master/docs/examples/opensslthreadlock.c
+ *
+ * COPYRIGHT AND PERMISSION NOTICE
+ * Copyright (c) 1996 - 2013, Daniel Stenberg, <daniel at haxx.se>.
+ *
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any purpose
+ * with or without fee is hereby granted, provided that the above copyright
+ * notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN
+ * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
+ * OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Except as contained in this notice, the name of a copyright holder shall not
+ * be used in advertising or otherwise to promote the sale, use or other dealings
+ * in this Software without prior written authorization of the copyright holder.
+ *
+ */
+
+#include "config.h"
+#include "ssl.h"
+#include "hlog.h"
+#include "hmalloc.h"
+#include "worker.h"
+
+#ifdef USE_SSL
+
+#include <ctype.h>
+#include <pthread.h>
+#include <openssl/conf.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#define SSL_DEFAULT_CIPHERS "HIGH:!aNULL:!MD5"
+#define SSL_PROTOCOLS (NGX_SSL_SSLv3|NGX_SSL_TLSv1 |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2)
+
+/* ssl error strings */
+#define SSL_ERR_LABELS_COUNT 6
+static const char *ssl_err_labels[][2] = {
+ { "invalid_err", "Invalid, unknown error" },
+ { "internal_err", "Internal error" },
+ { "peer_cert_unverified", "Peer certificate is not valid or not trusted" },
+ { "no_peer_cert", "Peer did not present a certificate" },
+ { "cert_no_subj", "Certificate does not contain a Subject field" },
+ { "cert_no_callsign", "Certificate does not contain a TQSL callsign in CN - not a ham cert" },
+ { "cert_callsign_mismatch", "Certificate callsign does not match login username" }
+};
+
+/* pthread wrapping for openssl */
+#define MUTEX_TYPE pthread_mutex_t
+#define MUTEX_SETUP(x) pthread_mutex_init(&(x), NULL)
+#define MUTEX_CLEANUP(x) pthread_mutex_destroy(&(x))
+#define MUTEX_LOCK(x) pthread_mutex_lock(&(x))
+#define MUTEX_UNLOCK(x) pthread_mutex_unlock(&(x))
+#define THREAD_ID pthread_self( )
+
+int ssl_available;
+int ssl_connection_index;
+int ssl_server_conf_index;
+int ssl_session_cache_index;
+
+/* This array will store all of the mutexes available to OpenSSL. */
+static MUTEX_TYPE *mutex_buf= NULL;
+
+static void ssl_thread_locking_function(int mode, int n, const char * file, int line)
+{
+ if (mode & CRYPTO_LOCK)
+ MUTEX_LOCK(mutex_buf[n]);
+ else
+ MUTEX_UNLOCK(mutex_buf[n]);
+}
+
+static unsigned long ssl_thread_id_function(void)
+{
+ return ((unsigned long)THREAD_ID);
+}
+
+static int ssl_thread_setup(void)
+{
+ int i;
+
+ hlog(LOG_DEBUG, "Creating OpenSSL mutexes (%d)...", CRYPTO_num_locks());
+
+ mutex_buf = hmalloc(CRYPTO_num_locks() * sizeof(MUTEX_TYPE));
+
+ for (i = 0; i < CRYPTO_num_locks(); i++)
+ MUTEX_SETUP(mutex_buf[i]);
+
+ CRYPTO_set_id_callback(ssl_thread_id_function);
+ CRYPTO_set_locking_callback(ssl_thread_locking_function);
+
+ return 0;
+}
+
+static int ssl_thread_cleanup(void)
+{
+ int i;
+
+ if (!mutex_buf)
+ return 0;
+
+ CRYPTO_set_id_callback(NULL);
+ CRYPTO_set_locking_callback(NULL);
+
+ for (i = 0; i < CRYPTO_num_locks( ); i++)
+ MUTEX_CLEANUP(mutex_buf[i]);
+
+ hfree(mutex_buf);
+ mutex_buf = NULL;
+
+ return 0;
+}
+
+/*
+ * string representations for error codes
+ */
+
+const char *ssl_strerror(int code)
+{
+ code *= -1;
+
+ if (code >= 0 && code < (sizeof ssl_err_labels / sizeof ssl_err_labels[0]))
+ return ssl_err_labels[code][1];
+
+ return ssl_err_labels[0][1];
+}
+
+/*
+ * Clear OpenSSL error queue
+ */
+
+static void ssl_error(int level, const char *msg)
+{
+ unsigned long n;
+ char errstr[512];
+
+ for ( ;; ) {
+ n = ERR_get_error();
+
+ if (n == 0)
+ break;
+
+ ERR_error_string_n(n, errstr, sizeof(errstr));
+ errstr[sizeof(errstr)-1] = 0;
+
+ hlog(level, "%s (%d): %s", msg, n, errstr);
+ }
+}
+
+static void ssl_clear_error(void)
+{
+ while (ERR_peek_error()) {
+ ssl_error(LOG_INFO, "Ignoring stale SSL error");
+ }
+
+ ERR_clear_error();
+}
+
+/*
+ * TrustedQSL custom X.509 certificate objects
+ */
+
+#define TRUSTEDQSL_OID "1.3.6.1.4.1.12348.1."
+#define TRUSTEDQSL_OID_CALLSIGN TRUSTEDQSL_OID "1"
+#define TRUSTEDQSL_OID_QSO_NOT_BEFORE TRUSTEDQSL_OID "2"
+#define TRUSTEDQSL_OID_QSO_NOT_AFTER TRUSTEDQSL_OID "3"
+#define TRUSTEDQSL_OID_DXCC_ENTITY TRUSTEDQSL_OID "4"
+#define TRUSTEDQSL_OID_SUPERCEDED_CERT TRUSTEDQSL_OID "5"
+#define TRUSTEDQSL_OID_CRQ_ISSUER_ORGANIZATION TRUSTEDQSL_OID "6"
+#define TRUSTEDQSL_OID_CRQ_ISSUER_ORGANIZATIONAL_UNIT TRUSTEDQSL_OID "7"
+
+static const char *tqsl_NIDs[][2] = {
+ { TRUSTEDQSL_OID_CALLSIGN, "AROcallsign" },
+ { TRUSTEDQSL_OID_QSO_NOT_BEFORE, "QSONotBeforeDate" },
+ { TRUSTEDQSL_OID_QSO_NOT_AFTER, "QSONotAfterDate" },
+ { TRUSTEDQSL_OID_DXCC_ENTITY, "dxccEntity" },
+ { TRUSTEDQSL_OID_SUPERCEDED_CERT, "supercededCertificate" },
+ { TRUSTEDQSL_OID_CRQ_ISSUER_ORGANIZATION, "tqslCRQIssuerOrganization" },
+ { TRUSTEDQSL_OID_CRQ_ISSUER_ORGANIZATIONAL_UNIT, "tqslCRQIssuerOrganizationalUnit" },
+};
+
+static int load_tqsl_custom_objects(void)
+{
+ int i;
+
+ for (i = 0; i < (sizeof tqsl_NIDs / sizeof tqsl_NIDs[0]); ++i)
+ if (OBJ_create(tqsl_NIDs[i][0], tqsl_NIDs[i][1], NULL) == 0)
+ return -1;
+
+ return 0;
+}
+
+static void ssl_info_callback(SSL *ssl, int where, int ret)
+{
+ struct client_t *c = SSL_get_ex_data(ssl, ssl_connection_index);
+
+ if (!c) {
+ hlog(LOG_ERR, "ssl_info_callback: no application data for connection");
+ return;
+ }
+
+ struct ssl_connection_t *ssl_conn = c->ssl_con;
+
+ if (!ssl_conn) {
+ hlog(LOG_ERR, "ssl_info_callback: no ssl_conn for connection");
+ return;
+ }
+
+ if (where & SSL_CB_HANDSHAKE_START) {
+ hlog(LOG_INFO, "%s/%d: SSL handshake start", c->addr_rem, c->fd);
+ if (ssl_conn->handshaked) {
+ ssl_conn->renegotiation = 1;
+ }
+ }
+
+ if (where & SSL_CB_HANDSHAKE_DONE) {
+ hlog(LOG_INFO, "%s/%d: SSL handshake done", c->addr_rem, c->fd);
+ }
+}
+
+/*
+ * Initialize SSL
+ */
+
+int ssl_init(void)
+{
+ hlog(LOG_INFO, "Initializing OpenSSL, built against %s ...", OPENSSL_VERSION_TEXT);
+
+ OPENSSL_config(NULL);
+
+ SSL_library_init();
+ SSL_load_error_strings();
+
+ ssl_thread_setup();
+
+ OpenSSL_add_all_algorithms();
+
+ load_tqsl_custom_objects();
+
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+#ifndef SSL_OP_NO_COMPRESSION
+ {
+ /*
+ * Disable gzip compression in OpenSSL prior to 1.0.0 version,
+ * this saves about 522K per connection.
+ */
+ int n;
+ STACK_OF(SSL_COMP) *ssl_comp_methods;
+
+ ssl_comp_methods = SSL_COMP_get_compression_methods();
+ n = sk_SSL_COMP_num(ssl_comp_methods);
+
+ while (n--) {
+ (void) sk_SSL_COMP_pop(ssl_comp_methods);
+ }
+
+ }
+#endif
+#endif
+
+ ssl_connection_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
+
+ if (ssl_connection_index == -1) {
+ ssl_error(LOG_ERR, "SSL_get_ex_new_index for connection");
+ return -1;
+ }
+
+ ssl_server_conf_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL);
+
+ if (ssl_server_conf_index == -1) {
+ ssl_error(LOG_ERR, "SSL_CTX_get_ex_new_index for conf");
+ return -1;
+ }
+
+ ssl_session_cache_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL);
+
+ if (ssl_session_cache_index == -1) {
+ ssl_error(LOG_ERR, "SSL_CTX_get_ex_new_index for session cache");
+ return -1;
+ }
+
+ ssl_available = 1;
+
+ return 0;
+}
+
+void ssl_atend(void)
+{
+ ssl_thread_cleanup();
+}
+
+struct ssl_t *ssl_alloc(void)
+{
+ struct ssl_t *ssl;
+
+ ssl = hmalloc(sizeof(*ssl));
+ memset(ssl, 0, sizeof(*ssl));
+
+ return ssl;
+}
+
+void ssl_free(struct ssl_t *ssl)
+{
+ if (ssl->ctx)
+ SSL_CTX_free(ssl->ctx);
+
+ hfree(ssl);
+}
+
+int ssl_create(struct ssl_t *ssl, void *data)
+{
+ ssl->ctx = SSL_CTX_new(SSLv23_method());
+
+ if (ssl->ctx == NULL) {
+ ssl_error(LOG_ERR, "ssl_create SSL_CTX_new failed");
+ return -1;
+ }
+
+ if (SSL_CTX_set_ex_data(ssl->ctx, ssl_server_conf_index, data) == 0) {
+ ssl_error(LOG_ERR, "ssl_create SSL_CTX_set_ex_data failed");
+ return -1;
+ }
+
+ /* client side options */
+
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_MICROSOFT_SESS_ID_BUG);
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_NETSCAPE_CHALLENGE_BUG);
+
+ /* server side options */
+
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG);
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER);
+
+ /* this option allow a potential SSL 2.0 rollback (CAN-2005-2969) */
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_MSIE_SSLV2_RSA_PADDING);
+
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_SSLEAY_080_CLIENT_DH_BUG);
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_TLS_D5_BUG);
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_TLS_BLOCK_PADDING_BUG);
+
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS);
+
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_SINGLE_DH_USE);
+
+ /* SSL protocols not configurable for now */
+ int protocols = SSL_PROTOCOLS;
+
+ if (!(protocols & NGX_SSL_SSLv2)) {
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_SSLv2);
+ }
+ if (!(protocols & NGX_SSL_SSLv3)) {
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_SSLv3);
+ }
+ if (!(protocols & NGX_SSL_TLSv1)) {
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_TLSv1);
+ }
+
+#ifdef SSL_OP_NO_TLSv1_1
+ if (!(protocols & NGX_SSL_TLSv1_1)) {
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_TLSv1_1);
+ }
+#endif
+#ifdef SSL_OP_NO_TLSv1_2
+ if (!(protocols & NGX_SSL_TLSv1_2)) {
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_TLSv1_2);
+ }
+#endif
+
+#ifdef SSL_OP_NO_COMPRESSION
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_COMPRESSION);
+#endif
+
+#ifdef SSL_MODE_RELEASE_BUFFERS
+ SSL_CTX_set_mode(ssl->ctx, SSL_MODE_RELEASE_BUFFERS);
+#endif
+
+ SSL_CTX_set_mode(ssl->ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
+
+ SSL_CTX_set_read_ahead(ssl->ctx, 1);
+
+ SSL_CTX_set_info_callback(ssl->ctx, (void *)ssl_info_callback);
+
+ if (SSL_CTX_set_cipher_list(ssl->ctx, SSL_DEFAULT_CIPHERS) == 0) {
+ ssl_error(LOG_ERR, "ssl_create SSL_CTX_set_cipher_list failed");
+ return -1;
+ }
+
+ /* prefer server-selected ciphers */
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
+
+ return 0;
+}
+
+/*
+ * Load server key and certificate
+ */
+
+int ssl_certificate(struct ssl_t *ssl, const char *certfile, const char *keyfile)
+{
+ if (SSL_CTX_use_certificate_chain_file(ssl->ctx, certfile) == 0) {
+ hlog(LOG_ERR, "Error while loading SSL certificate chain file \"%s\"", certfile);
+ ssl_error(LOG_ERR, "SSL_CTX_use_certificate_chain_file");
+ return -1;
+ }
+
+
+ if (SSL_CTX_use_PrivateKey_file(ssl->ctx, keyfile, SSL_FILETYPE_PEM) == 0) {
+ hlog(LOG_ERR, "Error while loading SSL private key file \"%s\"", keyfile);
+ ssl_error(LOG_ERR, "SSL_CTX_use_PrivateKey_file");
+ return -1;
+ }
+
+ if (!SSL_CTX_check_private_key(ssl->ctx)) {
+ hlog(LOG_ERR, "SSL private key (%s) does not work with this certificate (%s)", keyfile, certfile);
+ ssl_error(LOG_ERR, "SSL_CTX_check_private_key");
+ return -1;
+ }
+
+
+ return 0;
+}
+
+static int ssl_verify_callback(int ok, X509_STORE_CTX *x509_store)
+{
+ hlog(LOG_DEBUG, "ssl_verify_callback, ok: %d", ok);
+
+#if (NGX_DEBUG)
+ char *subject, *issuer;
+ int err, depth;
+ X509 *cert;
+ X509_NAME *sname, *iname;
+ ngx_connection_t *c;
+ ngx_ssl_conn_t *ssl_conn;
+
+ ssl_conn = X509_STORE_CTX_get_ex_data(x509_store,
+ SSL_get_ex_data_X509_STORE_CTX_idx());
+
+ c = ngx_ssl_get_connection(ssl_conn);
+
+ cert = X509_STORE_CTX_get_current_cert(x509_store);
+ err = X509_STORE_CTX_get_error(x509_store);
+ depth = X509_STORE_CTX_get_error_depth(x509_store);
+
+ sname = X509_get_subject_name(cert);
+ subject = sname ? X509_NAME_oneline(sname, NULL, 0) : "(none)";
+
+ iname = X509_get_issuer_name(cert);
+ issuer = iname ? X509_NAME_oneline(iname, NULL, 0) : "(none)";
+
+ ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "verify:%d, error:%d, depth:%d, "
+ "subject:\"%s\",issuer: \"%s\"",
+ ok, err, depth, subject, issuer);
+
+ if (sname) {
+ OPENSSL_free(subject);
+ }
+
+ if (iname) {
+ OPENSSL_free(issuer);
+ }
+#endif
+
+ return 1;
+}
+
+
+/*
+ * Load trusted CA certs for verifying our peers
+ */
+
+int ssl_ca_certificate(struct ssl_t *ssl, const char *cafile, int depth)
+{
+ STACK_OF(X509_NAME) *list;
+
+ SSL_CTX_set_verify(ssl->ctx, SSL_VERIFY_PEER, ssl_verify_callback);
+ SSL_CTX_set_verify_depth(ssl->ctx, depth);
+
+ if (SSL_CTX_load_verify_locations(ssl->ctx, cafile, NULL) == 0) {
+ hlog(LOG_ERR, "Failed to load trusted CA list from \"%s\"", cafile);
+ ssl_error(LOG_ERR, "SSL_CTX_load_verify_locations");
+ return -1;
+ }
+
+ list = SSL_load_client_CA_file(cafile);
+
+ if (list == NULL) {
+ hlog(LOG_ERR, "Failed to load client CA file from \"%s\"", cafile);
+ ssl_error(LOG_ERR, "SSL_load_client_CA_file");
+ return -1;
+ }
+
+ /*
+ * before 0.9.7h and 0.9.8 SSL_load_client_CA_file()
+ * always leaved an error in the error queue
+ */
+
+ ERR_clear_error();
+
+ SSL_CTX_set_client_CA_list(ssl->ctx, list);
+
+ ssl->validate = 1;
+
+ return 0;
+}
+
+/*
+ * Create a connect
+ */
+
+int ssl_create_connection(struct ssl_t *ssl, struct client_t *c, int i_am_client)
+{
+ struct ssl_connection_t *sc;
+
+ sc = hmalloc(sizeof(*sc));
+ sc->connection = SSL_new(ssl->ctx);
+
+ if (sc->connection == NULL) {
+ ssl_error(LOG_ERR, "SSL_new failed");
+ hfree(sc);
+ return -1;
+ }
+
+ if (SSL_set_fd(sc->connection, c->fd) == 0) {
+ ssl_error(LOG_ERR, "SSL_set_fd failed");
+ SSL_free(sc->connection);
+ hfree(sc);
+ return -1;
+ }
+
+ if (i_am_client) {
+ SSL_set_connect_state(sc->connection);
+ } else {
+ SSL_set_accept_state(sc->connection);
+ }
+
+ if (SSL_set_ex_data(sc->connection, ssl_connection_index, c) == 0) {
+ ssl_error(LOG_ERR, "SSL_set_ex_data failed");
+ SSL_free(sc->connection);
+ hfree(sc);
+ return -1;
+ }
+
+ sc->validate = ssl->validate;
+ c->ssl_con = sc;
+
+ return 0;
+}
+
+void ssl_free_connection(struct client_t *c)
+{
+ if (!c->ssl_con)
+ return;
+
+ SSL_free(c->ssl_con->connection);
+ hfree(c->ssl_con);
+ c->ssl_con = NULL;
+}
+
+int ssl_cert_callsign_match(const char *subj_call, const char *username)
+{
+ if (subj_call == NULL || username == NULL)
+ return 0;
+
+ while (*username != '-' && *username != 0 && *subj_call != 0) {
+ if (toupper(*username) != toupper(*subj_call))
+ return 0; /* mismatch */
+
+ subj_call++;
+ username++;
+ }
+
+ if (*subj_call != 0)
+ return 0; /* if username is shorter than subject callsign, we fail */
+
+ if (*username != '-' && *username != 0)
+ return 0; /* if we ran to end of subject callsign but not to end of username or start of SSID, we fail */
+
+ return 1;
+}
+
+
+/*
+ * Validate client certificate
+ */
+
+int ssl_validate_peer_cert_phase1(struct client_t *c)
+{
+ X509 *cert;
+
+ int rc = SSL_get_verify_result(c->ssl_con->connection);
+
+ if (rc != X509_V_OK) {
+ /* client gave a certificate, but it's not valid */
+ hlog(LOG_DEBUG, "%s/%s: Peer SSL certificate verification error %d: %s",
+ c->addr_rem, c->username, rc, X509_verify_cert_error_string(rc));
+ c->ssl_con->ssl_err_code = rc;
+ return SSL_VALIDATE_CLIENT_CERT_UNVERIFIED;
+ }
+
+ cert = SSL_get_peer_certificate(c->ssl_con->connection);
+
+ if (cert == NULL) {
+ /* client did not give a certificate */
+ return SSL_VALIDATE_NO_CLIENT_CERT;
+ }
+
+ X509_free(cert);
+
+ return 0;
+}
+
+int ssl_validate_peer_cert_phase2(struct client_t *c)
+{
+ int ret = -1;
+ X509 *cert;
+ X509_NAME *sname, *iname;
+ char *subject, *issuer;
+ char *subj_cn = NULL;
+ char *subj_call = NULL;
+ int nid, idx;
+ X509_NAME_ENTRY *entry;
+ ASN1_STRING *edata;
+
+ cert = SSL_get_peer_certificate(c->ssl_con->connection);
+
+ if (cert == NULL) {
+ /* client did not give a certificate */
+ return SSL_VALIDATE_NO_CLIENT_CERT;
+ }
+
+ /* ok, we have a cert, find subject */
+ sname = X509_get_subject_name(cert);
+ if (!sname) {
+ ret = SSL_VALIDATE_CERT_NO_SUBJECT;
+ goto fail;
+ }
+ subject = X509_NAME_oneline(sname, NULL, 0);
+
+ /* find tqsl callsign */
+ nid = OBJ_txt2nid("AROcallsign");
+ if (nid == NID_undef) {
+ hlog(LOG_ERR, "OBJ_txt2nid could not find NID for AROcallsign");
+ ret = SSL_VALIDATE_INTERNAL_ERROR;
+ goto fail;
+ }
+
+ idx = X509_NAME_get_index_by_NID(sname, nid, -1);
+ if (idx == -1) {
+ hlog(LOG_DEBUG, "%s/%s: peer certificate has no callsign: %s", c->addr_rem, c->username, subject);
+ ret = SSL_VALIDATE_CERT_NO_CALLSIGN;
+ goto fail;
+ }
+
+ entry = X509_NAME_get_entry(sname, idx);
+ if (entry != NULL) {
+ edata = X509_NAME_ENTRY_get_data(entry);
+ if (edata != NULL)
+ ASN1_STRING_to_UTF8((unsigned char **)&subj_call, edata);
+ }
+
+ /* find CN of subject */
+ idx = X509_NAME_get_index_by_NID(sname, NID_commonName, -1);
+ if (idx == -1) {
+ hlog(LOG_DEBUG, "%s/%s: peer certificate has no CN: %s", c->addr_rem, c->username, subject);
+ } else {
+ entry = X509_NAME_get_entry(sname, idx);
+ if (entry != NULL) {
+ edata = X509_NAME_ENTRY_get_data(entry);
+ if (edata != NULL)
+ ASN1_STRING_to_UTF8((unsigned char **)&subj_cn, edata);
+ }
+ }
+
+ if (!subj_call) {
+ hlog(LOG_DEBUG, "%s/%s: peer certificate callsign conversion failed: %s", c->addr_rem, c->username, subject);
+ ret = SSL_VALIDATE_CERT_NO_CALLSIGN;
+ goto fail;
+ }
+
+ if (!ssl_cert_callsign_match(subj_call, c->username)) {
+ ret = SSL_VALIDATE_CERT_CALLSIGN_MISMATCH;
+ goto fail;
+ }
+
+ /* find issuer */
+ iname = X509_get_issuer_name(cert);
+ issuer = iname ? X509_NAME_oneline(iname, NULL, 0) : "(none)";
+
+ ret = 0;
+ hlog(LOG_INFO, "%s/%s: Peer validated using SSL certificate: subject '%s' callsign '%s' CN '%s' issuer '%s'",
+ c->addr_rem, c->username, subject, subj_call, (subj_cn) ? subj_cn : "(none)", issuer);
+
+ /* store copies of cert subject and issuer */
+ strncpy(c->cert_subject, subject, sizeof(c->cert_subject));
+ c->cert_subject[sizeof(c->cert_subject)-1] = 0;
+ strncpy(c->cert_issuer, issuer, sizeof(c->cert_issuer));
+ c->cert_issuer[sizeof(c->cert_issuer)-1] = 0;
+
+fail:
+ /* free up whatever we allocated */
+ X509_free(cert);
+
+ if (subj_call)
+ OPENSSL_free(subj_call);
+ if (subj_cn)
+ OPENSSL_free(subj_cn);
+
+ return ret;
+}
+
+/*
+ * Write data to an SSL socket
+ */
+
+int ssl_write(struct worker_t *self, struct client_t *c)
+{
+ int n;
+ int sslerr;
+ int err;
+ int to_write;
+
+ to_write = c->obuf_end - c->obuf_start;
+
+ //hlog(LOG_DEBUG, "ssl_write fd %d of %d bytes", c->fd, to_write);
+ ssl_clear_error();
+
+ n = SSL_write(c->ssl_con->connection, c->obuf + c->obuf_start, to_write);
+
+ //hlog(LOG_DEBUG, "SSL_write fd %d returned %d", c->fd, n);
+
+ if (n > 0) {
+ /* ok, we wrote some */
+ c->obuf_start += n;
+ c->obuf_wtime = tick;
+
+ /* All done ? */
+ if (c->obuf_start >= c->obuf_end) {
+ //hlog(LOG_DEBUG, "ssl_write fd %d (%s) obuf empty", c->fd, c->addr_rem);
+ c->obuf_start = 0;
+ c->obuf_end = 0;
+
+ /* tell the poller that we have no outgoing data */
+ xpoll_outgoing(&self->xp, c->xfd, 0);
+ return n;
+ }
+
+ xpoll_outgoing(&self->xp, c->xfd, 1);
+
+ return n;
+ }
+
+ sslerr = SSL_get_error(c->ssl_con->connection, n);
+ err = (sslerr == SSL_ERROR_SYSCALL) ? errno : 0;
+
+ if (sslerr == SSL_ERROR_WANT_WRITE) {
+ hlog(LOG_INFO, "ssl_write fd %d: SSL_write wants to write again, marking socket for write events", c->fd);
+
+ /* tell the poller that we have outgoing data */
+ xpoll_outgoing(&self->xp, c->xfd, 1);
+
+ return 0;
+ }
+
+ if (sslerr == SSL_ERROR_WANT_READ) {
+ hlog(LOG_INFO, "ssl_write fd %d: SSL_write wants to read, returning 0", c->fd);
+
+ /* tell the poller that we won't be writing now, until we've read... */
+ xpoll_outgoing(&self->xp, c->xfd, 0);
+
+ return 0;
+ }
+
+ if (err) {
+ hlog(LOG_DEBUG, "ssl_write fd %d: I/O syscall error: %s", c->fd, strerror(err));
+ } else {
+ char ebuf[255];
+
+ ERR_error_string_n(sslerr, ebuf, sizeof(ebuf));
+ hlog(LOG_INFO, "ssl_write fd %d failed with ret %d sslerr %u errno %d: %s (%s)",
+ c->fd, n, sslerr, err, ebuf, ERR_reason_error_string(sslerr));
+ }
+
+ c->ssl_con->no_wait_shutdown = 1;
+ c->ssl_con->no_send_shutdown = 1;
+
+ hlog(LOG_DEBUG, "ssl_write fd %d: SSL_write() failed", c->fd);
+ client_close(self, c, err);
+
+ return -13;
+}
+
+int ssl_writable(struct worker_t *self, struct client_t *c)
+{
+ int to_write;
+
+ to_write = c->obuf_end - c->obuf_start;
+
+ //hlog(LOG_DEBUG, "ssl_writable fd %d, %d available for writing", c->fd, to_write);
+
+ /* SSL_write does not appreciate writing a 0-length buffer */
+ if (to_write == 0) {
+ /* tell the poller that we have no outgoing data */
+ xpoll_outgoing(&self->xp, c->xfd, 0);
+ return 0;
+ }
+
+ return ssl_write(self, c);
+}
+
+int ssl_readable(struct worker_t *self, struct client_t *c)
+{
+ int r;
+ int sslerr, err;
+
+ //hlog(LOG_DEBUG, "ssl_readable fd %d", c->fd);
+
+ ssl_clear_error();
+
+ r = SSL_read(c->ssl_con->connection, c->ibuf + c->ibuf_end, c->ibuf_size - c->ibuf_end - 1);
+
+ if (r > 0) {
+ /* we got some data... process */
+ //hlog(LOG_DEBUG, "SSL_read fd %d returned %d bytes of data", c->fd, r);
+
+ /* TODO: whatever the client_readable does */
+ return client_postread(self, c, r);
+ }
+
+ sslerr = SSL_get_error(c->ssl_con->connection, r);
+ err = (sslerr == SSL_ERROR_SYSCALL) ? errno : 0;
+
+ if (sslerr == SSL_ERROR_WANT_READ) {
+ hlog(LOG_DEBUG, "ssl_readable fd %d: SSL_read wants to read again, doing it later", c->fd);
+
+ if (c->obuf_end - c->obuf_start > 0) {
+ /* tell the poller that we have outgoing data */
+ xpoll_outgoing(&self->xp, c->xfd, 1);
+ }
+
+ return 0;
+ }
+
+ if (sslerr == SSL_ERROR_WANT_WRITE) {
+ hlog(LOG_INFO, "ssl_readable fd %d: SSL_read wants to write (peer starts SSL renegotiation?), calling ssl_write", c->fd);
+ return ssl_write(self, c);
+ }
+
+ c->ssl_con->no_wait_shutdown = 1;
+ c->ssl_con->no_send_shutdown = 1;
+
+ if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) {
+ hlog(LOG_DEBUG, "ssl_readable fd %d: peer shutdown SSL cleanly", c->fd);
+ client_close(self, c, CLIERR_EOF);
+ return -1;
+ }
+
+ if (err) {
+ hlog(LOG_DEBUG, "ssl_readable fd %d: I/O syscall error: %s", c->fd, strerror(err));
+ } else {
+ char ebuf[255];
+
+ ERR_error_string_n(sslerr, ebuf, sizeof(ebuf));
+ hlog(LOG_INFO, "ssl_readable fd %d failed with ret %d sslerr %d errno %d: %s (%s)",
+ c->fd, r, sslerr, err, ebuf, ERR_reason_error_string(sslerr));
+ }
+
+ client_close(self, c, err);
+ return -1;
+}
+
+
+#endif
+
diff --git a/ssl.h b/ssl.h
new file mode 100644
index 0000000..1ae4dec
--- /dev/null
+++ b/ssl.h
@@ -0,0 +1,102 @@
+
+#include "config.h"
+
+#ifdef HAVE_OPENSSL_SSL_H
+#define USE_SSL
+#endif
+
+#ifndef SSL_H
+#define SSL_H
+
+#ifdef USE_SSL
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/conf.h>
+#include <openssl/engine.h>
+#include <openssl/evp.h>
+
+/* ssl error codes, must match ssl_err_labels order */
+#define SSL_VALIDATE_INTERNAL_ERROR -1
+#define SSL_VALIDATE_CLIENT_CERT_UNVERIFIED -2
+#define SSL_VALIDATE_NO_CLIENT_CERT -3
+#define SSL_VALIDATE_CERT_NO_SUBJECT -4
+#define SSL_VALIDATE_CERT_NO_CALLSIGN -5
+#define SSL_VALIDATE_CERT_CALLSIGN_MISMATCH -6
+
+struct client_t;
+struct worker_t;
+
+struct ssl_t {
+ SSL_CTX *ctx;
+
+ unsigned validate;
+};
+
+struct ssl_connection_t {
+ SSL *connection;
+
+ unsigned handshaked:1;
+
+ unsigned renegotiation:1;
+ unsigned buffer:1;
+ unsigned no_wait_shutdown:1;
+ unsigned no_send_shutdown:1;
+
+ unsigned validate;
+ int ssl_err_code;
+};
+
+#define NGX_SSL_SSLv2 0x0002
+#define NGX_SSL_SSLv3 0x0004
+#define NGX_SSL_TLSv1 0x0008
+#define NGX_SSL_TLSv1_1 0x0010
+#define NGX_SSL_TLSv1_2 0x0020
+
+
+#define NGX_SSL_BUFFER 1
+#define NGX_SSL_CLIENT 2
+
+#define NGX_SSL_BUFSIZE 16384
+
+/* string representations for error codes */
+extern const char *ssl_strerror(int code);
+
+/* initialize and deinit the library */
+extern int ssl_init(void);
+extern void ssl_atend(void);
+
+/* per-listener structure allocators */
+extern struct ssl_t *ssl_alloc(void);
+extern void ssl_free(struct ssl_t *ssl);
+
+/* create context for listener, load certs */
+extern int ssl_create(struct ssl_t *ssl, void *data);
+extern int ssl_certificate(struct ssl_t *ssl, const char *certfile, const char *keyfile);
+extern int ssl_ca_certificate(struct ssl_t *ssl, const char *cafile, int depth);
+
+/* create / free connection */
+extern int ssl_create_connection(struct ssl_t *ssl, struct client_t *c, int i_am_client);
+extern void ssl_free_connection(struct client_t *c);
+
+/* validate a client certificate */
+extern int ssl_validate_peer_cert_phase1(struct client_t *c);
+extern int ssl_validate_peer_cert_phase2(struct client_t *c);
+
+extern int ssl_write(struct worker_t *self, struct client_t *c);
+extern int ssl_writable(struct worker_t *self, struct client_t *c);
+extern int ssl_readable(struct worker_t *self, struct client_t *c);
+
+
+#else
+
+struct ssl_t {
+};
+
+
+#define ssl_init(...) { }
+#define ssl_atend(...) { }
+
+#endif /* USE_SSL */
+#endif /* SSL_H */
+
diff --git a/svnversion b/svnversion
new file mode 100755
index 0000000..e6d3b81
--- /dev/null
+++ b/svnversion
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+echo 523
diff --git a/svnversion-test.sh b/svnversion-test.sh
new file mode 100644
index 0000000..fdbcca4
--- /dev/null
+++ b/svnversion-test.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+#set -x
+echo "svnversion-test.sh: $*"
+
+SV="$1"
+shift
+
+SVNVERSION=undef
+
+for x in /bin/svnversion /usr/bin/svnversion
+do
+ if [ -x $x ] ; then
+ SVNVERSION="`$x`"
+ fi
+done
+
+if [ "$SVNVERSION" = "undef" -o "$SVNVERSION" = "Unversioned directory" ] ; then
+ if [ -f SVNVERSION ] ; then
+ echo "Can't pull SVNVERSION value from svn storage, pulling from SVNVERSION file.."
+ SVNVERSION="`cat SVNVERSION`"
+ fi
+else
+ echo "$SVNVERSION" > SVNVERSION
+fi
+
+if [ "$SVNVERSION" != "$SV" ] ; then
+ echo "Miss-match of '$SVNVERSION' vs. '$SV' -- aborting now, please rerun the make command."
+ exit 1
+fi
+
+X="`(echo -n $SVNVERSION | tr -d 0-9)`"
+if [ -n "$X" ] ; then
+ echo "Mixed or modified tree: ($SVNVERSION), ARE YOU SURE ??." ; \
+ echo -n "Y/^C ? "; read var ; \
+fi
diff --git a/telemetry.c b/telemetry.c
new file mode 100644
index 0000000..b4951f6
--- /dev/null
+++ b/telemetry.c
@@ -0,0 +1,572 @@
+/* **************************************************************** *
+ * *
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ * **************************************************************** */
+
+#include "aprx.h"
+
+#define telemetry_timescaler 2 // scale to 10 minute sums
+
+static int telemetry_interval = 20 * 60; // every 20 minutes
+static int telemetry_labelinterval = 120*60; // every 2 hours
+static int telemetry_labelindex = 0;
+
+#if (defined(ERLANGSTORAGE) || (USE_ONE_MINUTE_STORAGE == 1))
+static int telemetry_1min_steps = 20;
+#endif
+#if (defined(ERLANGSTORAGE) || (USE_ONE_MINUTE_STORAGE == 0))
+static int telemetry_10min_steps = 2;
+#endif
+
+static struct timeval telemetry_time;
+static struct timeval telemetry_labeltime;
+static int telemetry_seq;
+static int telemetry_params;
+
+
+struct rftelemetry {
+ struct aprx_interface *transmitter;
+ struct aprx_interface **sources;
+ int source_count;
+ char *viapath;
+};
+
+static int rftelemetrycount;
+static struct rftelemetry **rftelemetry;
+
+static void rf_telemetry(const struct aprx_interface *sourceaif, const char *beaconaddr,
+ const const char *buf, const int buflen);
+
+static void telemetry_resettime(void *arg)
+{
+ struct timeval *tv = (struct timeval*)arg;
+ tv_timeradd_seconds( tv, &tick, telemetry_interval );
+}
+
+static void telemetry_resetlabeltime(void *arg)
+{
+ struct timeval *tv = (struct timeval*)arg;
+ tv_timeradd_seconds( tv, &tick, 120 ); // first label 2 minutes from now
+}
+
+
+void telemetry_start()
+{
+ /*
+ * Initialize the sequence start to be highly likely
+ * different from previous one... This really should
+ * be in some persistent database, but this is reasonable
+ * compromise.
+ */
+ telemetry_seq = (time(NULL)) & 255;
+
+ // "tick" is supposedly current time..
+ telemetry_resettime( &telemetry_time );
+ telemetry_resetlabeltime( &telemetry_labeltime );
+
+ if (debug) printf("telemetry_start()\n");
+}
+
+int telemetry_prepoll(struct aprxpolls *app)
+{
+ // Check that time has not jumped too far ahead/back (1.5 telemetry intervals)
+ if (time_reset) {
+ telemetry_resettime(&telemetry_time);
+ telemetry_resetlabeltime(&telemetry_labeltime);
+ }
+
+ // Normal operational step
+
+ if (tv_timercmp(&app->next_timeout, &telemetry_time) > 0)
+ app->next_timeout = telemetry_time;
+ if (tv_timercmp(&app->next_timeout, &telemetry_labeltime) > 0)
+ app->next_timeout = telemetry_labeltime;
+
+ if (debug>1) printf("telemetry_prepoll()\n");
+
+ return 0;
+}
+
+static void telemetry_datatx(void);
+static void telemetry_labeltx(void);
+
+int telemetry_postpoll(struct aprxpolls *app)
+{
+ if (debug>1) {
+ printf("telemetry_postpoll() telemetrytime=%ds labeltime=%ds\n",
+ tv_timerdelta_millis(&tick, &telemetry_time)/1000,
+ tv_timerdelta_millis(&tick, &telemetry_labeltime)/1000);
+ }
+ if (tv_timercmp(&telemetry_time, &tick) <= 0) {
+ tv_timeradd_seconds(&telemetry_time, &telemetry_time, telemetry_interval);
+ telemetry_datatx();
+ }
+
+ if (tv_timercmp(&telemetry_labeltime, &tick) <= 0) {
+ tv_timeradd_seconds(&telemetry_labeltime, &telemetry_labeltime, telemetry_labelinterval);
+ telemetry_labeltx();
+ }
+
+ return 0;
+}
+
+static void telemetry_datatx(void)
+{
+ int i, j, k, t;
+ char buf[200], *s;
+ int buflen;
+ char beaconaddr[60];
+ int beaconaddrlen;
+ long erlmax;
+ float erlcapa;
+ float f;
+
+
+ if (debug)
+ printf("Telemetry Tx run; next one in %.2f minutes\n", (telemetry_interval/60.0));
+
+ // Init these for RF transmission
+ buf[0] = 0x03; // AX.25 Control
+ buf[1] = 0xF0; // AX.25 PID
+
+ ++telemetry_seq;
+ telemetry_seq %= 1000;
+ for (i = 0; i < ErlangLinesCount; ++i) {
+ struct erlangline *E = ErlangLines[i];
+ struct aprx_interface *sourceaif = find_interface_by_callsign(E->name);
+ if (!sourceaif || !interface_is_telemetrable(sourceaif))
+ continue;
+
+ beaconaddrlen = sprintf(beaconaddr, "%s>%s,TCPIP*", E->name, tocall);
+ // First two bytes of BUF are for AX.25 control+PID fields
+ s = buf+2;
+ s += sprintf(s, "T#%03d,", telemetry_seq);
+
+
+ // Raw Rx Erlang - plotting scale factor: 1/200
+ erlmax = 0;
+#if (USE_ONE_MINUTE_DATA == 1)
+ // Find busiest 1 minute
+ k = E->e1_cursor;
+ t = E->e1_max;
+ if (t > telemetry_1min_steps)
+ t = telemetry_1min_steps; // Up to 10 of 1 minute samples
+ erlcapa = 1.0 / E->erlang_capa; // 1/capa of 1 minute
+ for (j = 0; j < t; ++j) {
+ --k;
+ if (k < 0)
+ k = E->e1_max - 1;
+ if (E->e1[k].bytes_rx > erlmax)
+ erlmax = E->e1[k].bytes_rx;
+ }
+#else
+ // Find busiest 10 minute
+ k = E->e10_cursor;
+ t = E->e10_max;
+ if (t > telemetry_10min_steps)
+ t = telemetry_10min_steps; // Up to 1 of 10 minute samples
+ erlcapa = 0.1 / E->erlang_capa; // 1/capa of 10 minute
+ for (j = 0; j < t; ++j) {
+ --k;
+ if (k < 0)
+ k = E->e10_max - 1;
+ if (E->e10[k].bytes_rx > erlmax)
+ erlmax = E->e10[k].bytes_rx;
+ }
+#endif
+ f = (200.0 * erlcapa * erlmax);
+ s += sprintf(s, "%.1f,", f);
+
+ // Raw Tx Erlang - plotting scale factor: 1/200
+ erlmax = 0;
+#if (USE_ONE_MINUTE_DATA == 1)
+ // Find busiest 1 minute
+ k = E->e1_cursor;
+ t = E->e1_max;
+ if (t > telemetry_1min_steps)
+ t = telemetry_1min_steps; // Up to 10 of 1 minute samples
+ erlcapa = 1.0 / E->erlang_capa; // 1/capa of 1 minute
+ for (j = 0; j < t; ++j) {
+ --k;
+ if (k < 0)
+ k = E->e1_max - 1;
+ if (E->e1[k].bytes_tx > erlmax)
+ erlmax = E->e1[k].bytes_tx;
+ }
+#else
+ // Find busiest 10 minute
+ k = E->e10_cursor;
+ t = E->e10_max;
+ if (t > telemetry_10min_steps)
+ t = telemetry_10min_steps; // Up to 1 of 10 minute samples
+ erlcapa = 0.1 / E->erlang_capa; // 1/capa of 10 minute
+ for (j = 0; j < t; ++j) {
+ --k;
+ if (k < 0)
+ k = E->e10_max - 1;
+ if (E->e10[k].bytes_tx > erlmax)
+ erlmax = E->e10[k].bytes_tx;
+ }
+#endif
+ f = (200.0 * erlcapa * erlmax);
+ s += sprintf(s, "%.1f,", f);
+
+ erlmax = 0;
+#if (USE_ONE_MINUTE_DATA == 1)
+ // Sum of 1 minute packet counts
+ k = E->e1_cursor;
+ t = E->e1_max;
+ if (t > telemetry_1min_steps)
+ t = telemetry_1min_steps; /* Up to 10 of 1 minute samples */
+ for (j = 0; j < t; ++j) {
+ --k;
+ if (k < 0)
+ k = E->e1_max - 1;
+ erlmax += E->e1[k].packets_rx;
+ }
+#else
+ // Sum of 10 minute packet counts
+ erlmax = 0;
+ k = E->e10_cursor;
+ t = E->e10_max;
+ if (t > telemetry_10min_steps)
+ t = telemetry_10min_steps; // Up to 1 of 10 minute samples
+ for (j = 0; j < t; ++j) {
+ --k;
+ if (k < 0)
+ k = E->e10_max - 1;
+ erlmax += E->e10[k].packets_rx;
+ }
+#endif
+ f = erlmax / telemetry_timescaler;
+ s += sprintf(s, "%.1f,", f);
+
+ erlmax = 0;
+#if (USE_ONE_MINUTE_DATA == 1)
+ // Sum of 1 minute packet drop counts
+ k = E->e1_cursor;
+ t = E->e1_max;
+ if (t > telemetry_1min_steps)
+ t = telemetry_1min_steps; /* Up to 10 of 1 minute samples */
+ for (j = 0; j < t; ++j) {
+ --k;
+ if (k < 0)
+ k = E->e1_max - 1;
+ erlmax += E->e1[k].packets_rxdrop;
+ }
+#else
+ // Sum of 10 minute packet drop counts
+ k = E->e10_cursor;
+ t = E->e10_max;
+ if (t > telemetry_10min_steps)
+ t = telemetry_10min_steps; // Up to 1 of 10 minute samples
+ for (j = 0; j < t; ++j) {
+ --k;
+ if (k < 0)
+ k = E->e10_max - 1;
+ erlmax += E->e10[k].packets_rxdrop;
+ }
+#endif
+ f = erlmax / telemetry_timescaler;
+ s += sprintf(s, "%.1f,", f);
+
+ erlmax = 0;
+#if (USE_ONE_MINUTE_DATA == 1)
+ // Sum of 1 minute packet tx counts
+ k = E->e1_cursor;
+ t = E->e1_max;
+ if (t > telemetry_1min_steps)
+ t = telemetry_1min_steps; /* Up to 10 of 1 minute samples */
+ for (j = 0; j < t; ++j) {
+ --k;
+ if (k < 0)
+ k = E->e1_max - 1;
+ erlmax += E->e1[k].packets_tx;
+ }
+#else
+ // Sum of 10 minute packet tx counts
+ k = E->e10_cursor;
+ t = E->e10_max;
+ if (t > telemetry_10min_steps)
+ t = telemetry_10min_steps; // Up to 1 of 10 minute samples
+ for (j = 0; j < t; ++j) {
+ --k;
+ if (k < 0)
+ k = E->e10_max - 1;
+ erlmax += E->e10[k].packets_tx;
+ }
+#endif
+ f = erlmax / telemetry_timescaler;
+ s += sprintf(s, "%.1f,", f);
+
+ /* Tail filler */
+ s += sprintf(s, "00000000"); // FIXME: flag telemetry?
+
+ if (debug>2) printf("%s (to is=%d rf=%d) %s\n",
+ beaconaddr, sourceaif->telemeter_to_is,
+ sourceaif->telemeter_to_rf,
+ buf+2);
+
+ /* _NO_ ending CRLF, the APRSIS subsystem adds it. */
+
+ /* Send those (net)beacons.. */
+ buflen = s - buf;
+#ifndef DISABLE_IGATE
+ if (sourceaif->telemeter_to_is) {
+ aprsis_queue(beaconaddr, beaconaddrlen,
+ qTYPE_LOCALGEN, aprsis_login,
+ buf+2, buflen-2);
+ }
+#endif
+ rf_telemetry(sourceaif, beaconaddr, buf, buflen);
+
+ }
+ ++telemetry_params;
+}
+
+// Telemetry Labels are transmitted separately
+static void telemetry_labeltx()
+{
+ int i;
+ char buf[200], *s;
+ int buflen;
+ char beaconaddr[60];
+ int beaconaddrlen;
+
+
+ if (debug)
+ printf("Telemetry LabelTx run; next one in %.2f minutes\n", (telemetry_labelinterval/60.0));
+
+ // Init these for RF transmission
+ buf[0] = 0x03; // AX.25 Control
+ buf[1] = 0xF0; // AX.25 PID
+
+ ++telemetry_seq;
+ telemetry_seq %= 1000;
+ for (i = 0; i < ErlangLinesCount; ++i) {
+ struct erlangline *E = ErlangLines[i];
+ struct aprx_interface *sourceaif = find_interface_by_callsign(E->name);
+ if (!sourceaif || !interface_is_telemetrable(sourceaif))
+ continue;
+ beaconaddrlen = sprintf(beaconaddr, "%s>%s,TCPIP*", E->name, tocall);
+ // First two bytes of BUF are for AX.25 control+PID fields
+
+ /* Send every 5h20m or thereabouts. */
+
+ switch (telemetry_labelindex) {
+ case 0:
+ s = buf+2 + sprintf(buf+2,
+ ":%-9s:PARM.Avg 10m,Avg 10m,RxPkts,IGateDropRx,TxPkts",
+ E->name);
+ break;
+ case 1:
+ s = buf+2 + sprintf(buf+2,
+ ":%-9s:UNIT.Rx Erlang,Tx Erlang,count/10m,count/10m,count/10m",
+ E->name);
+ break;
+ case 2:
+
+ s = buf+2 + sprintf(buf+2,
+ ":%-9s:EQNS.0,0.005,0,0,0.005,0,0,1,0,0,1,0,0,1,0",
+ E->name);
+ break;
+ default:
+ break;
+ }
+
+ if (debug>2) printf("%s (to is=%d rf=%d) %s\n",
+ beaconaddr, sourceaif->telemeter_to_is,
+ sourceaif->telemeter_to_rf,
+ buf+2);
+
+ buflen = s - buf;
+#ifndef DISABLE_IGATE
+ if (sourceaif->telemeter_to_is) {
+ aprsis_queue(beaconaddr, beaconaddrlen,
+ qTYPE_LOCALGEN, aprsis_login,
+ buf+2, buflen-2);
+ }
+#endif
+ rf_telemetry(sourceaif, beaconaddr, buf, buflen);
+ }
+ ++telemetry_params;
+
+ // Switch label-index..
+ ++telemetry_labelindex;
+ if (telemetry_labelindex > 2)
+ telemetry_labelindex = 0;
+}
+
+/*
+ * Transmit telemetry to the RF interface that is being monitored.
+ * Interface 'flags' contain controls on thist.
+ */
+static void rf_telemetry(const struct aprx_interface *sourceaif,
+ const char *beaconaddr,
+ const char *buf,
+ const const int buflen)
+{
+ int i;
+ int t_idx;
+ char *dest;
+
+ if (rftelemetrycount == 0) return; // Nothing to do!
+ if (sourceaif == NULL) return; // Huh? Unknown source..
+
+ if (!sourceaif->telemeter_to_rf) return; // not wanted
+ if (!interface_is_telemetrable(sourceaif)) return; // not possible
+
+
+ // The beaconaddr comes in as:
+ // "interfacecall>APRXxx,TCPIP*"
+ dest = strchr(beaconaddr, ',');
+ if (dest != NULL) *dest = 0;
+ dest = strchr(beaconaddr, '>');
+ if (dest != NULL) *dest++ = 0;
+ if (dest == NULL) {
+ // Impossible -- said she...
+ return;
+ }
+
+ for (t_idx = 0; t_idx < rftelemetrycount; ++t_idx) {
+ struct rftelemetry *rftlm = rftelemetry[t_idx];
+ if (rftlm == NULL) break;
+ for (i = 0; i < rftlm->source_count; ++i) {
+ if (rftlm->sources[i] == sourceaif) {
+ // Found telemetry transmitter which wants this source
+
+ interface_transmit_beacon(rftlm->transmitter,
+ beaconaddr,
+ dest,
+ rftlm->viapath,
+ buf, buflen);
+ }
+ }
+ }
+}
+
+int telemetry_config(struct configfile *cf)
+{
+ char *name, *param1;
+ char *str = cf->buf;
+ int has_fault = 0;
+
+ struct aprx_interface *aif = NULL;
+ struct aprx_interface **sources = NULL;
+ int source_count = 0;
+ char *viapath = NULL;
+
+ while (readconfigline(cf) != NULL) {
+ if (configline_is_comment(cf))
+ continue; /* Comment line, or empty line */
+
+ // It can be severely indented...
+ str = config_SKIPSPACE(cf->buf);
+
+ name = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+ config_STRLOWER(name);
+
+ param1 = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ if (strcmp(name, "</telemetry>") == 0)
+ break;
+
+ if (strcmp(name, "transmit") == 0 ||
+ strcmp(name, "transmitter") == 0) {
+ if (strcasecmp(param1,"$mycall") == 0)
+ param1 = (char*)mycall;
+
+ aif = find_interface_by_callsign(param1);
+ if (aif != NULL && (!aif->tx_ok)) {
+ aif = NULL; // Not
+ printf("%s:%d ERROR: This transmit interface has no TX-OK TRUE setting: '%s'\n",
+ cf->name, cf->linenum, param1);
+ has_fault = 1;
+ } else if (aif == NULL) {
+ printf("%s:%d ERROR: Unknown interface: '%s'\n",
+ cf->name, cf->linenum, param1);
+ has_fault = 1;
+ }
+
+ } else if (strcmp(name, "via") == 0) {
+ if (viapath != NULL) {
+ printf("%s:%d ERROR: Double definition of 'via'\n",
+ cf->name, cf->linenum);
+ has_fault = 1;
+ } else if (*param1 == 0) {
+ printf("%s:%d ERROR: 'via' keyword without parameter\n",
+ cf->name, cf->linenum);
+ has_fault = 1;
+ }
+ if (!has_fault) {
+ const char *check;
+ config_STRUPPER(param1);
+ check = tnc2_verify_callsign_format(param1, 0, 1, param1+strlen(param1));
+ if (check == NULL) {
+ has_fault = 1;
+ printf("%s:%d ERROR: The 'via %s' parameter is not acceptable AX.25 format\n",
+ cf->name, cf->linenum, param1);
+
+ }
+ }
+ if (!has_fault) {
+ // Save it
+ viapath = strdup(param1);
+ }
+ } else if (strcmp(name, "source") == 0) {
+ struct aprx_interface *source_aif = NULL;
+ if (debug)
+ printf("%s:%d <telemetry> source = '%s'\n",
+ cf->name, cf->linenum, param1);
+
+ if (strcasecmp(param1,"$mycall") == 0)
+ param1 = (char*)mycall;
+
+ source_aif = find_interface_by_callsign(param1);
+ if (source_aif == NULL) {
+ has_fault = 1;
+ printf("%s:%d ERROR: Digipeater source '%s' not found\n",
+ cf->name, cf->linenum, param1);
+ } else {
+ // Collect them all...
+ sources = realloc(sources, sizeof(void*)*(source_count+3));
+ sources[source_count++] = source_aif;
+ sources[source_count+1] = NULL;
+ }
+ if (debug>1)
+ printf(" .. source_aif = %p\n", source_aif);
+ } else {
+ printf("%s:%d ERROR: Unknown <telemetry> block keyword '%s'\n",
+ cf->name, cf->linenum, name);
+ }
+ }
+
+ if (has_fault) {
+ if (sources != NULL)
+ free(sources);
+ if (viapath != NULL)
+ free(viapath);
+ printf("ERROR: Failures on defining <telemetry> block parameters\n");
+ printf(" APRS RF-Telemetry will not be activated.\n");
+ } else {
+ struct rftelemetry *newrf = calloc(1, sizeof(*newrf));
+ newrf->transmitter = aif;
+ newrf->viapath = viapath;
+ newrf->sources = sources;
+ newrf->source_count = source_count;
+ rftelemetry = realloc(rftelemetry, sizeof(void*)*(rftelemetrycount+2));
+ rftelemetry[rftelemetrycount++] = newrf;
+
+ if (debug) printf("Defined <telemetry> to transmitter %s\n", aif ? aif->callsign : "ALL");
+ }
+ return has_fault;
+}
diff --git a/test.c b/test.c
new file mode 100644
index 0000000..254957d
--- /dev/null
+++ b/test.c
@@ -0,0 +1,21 @@
+#include <stdio.h>
+
+static int aprspass(const char *mycall)
+{
+ int a = 0, h = 29666, c;
+
+ for (; *mycall; ++mycall) {
+ c = 0xFF & *mycall;
+ if (!(('0' <= c && c <= '9') || ('A' <= c && c <= 'Z')))
+ break;
+ h ^= ((0xFF & *mycall) * (a ? 1 : 256));
+ a = !a;
+ }
+ return h;
+}
+
+main()
+{
+ printf("APRSPASS: %d\n", aprspass("OH2MQK-1"));
+ return 0;
+}
diff --git a/timercmp.c b/timercmp.c
new file mode 100644
index 0000000..f03fdda
--- /dev/null
+++ b/timercmp.c
@@ -0,0 +1,157 @@
+/* **************************************************************** *
+ * *
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ * **************************************************************** */
+#include "aprx.h"
+
+/* Bits used only in the main program.. */
+#include <signal.h>
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+#ifdef HAVE_TIME_H
+# include <time.h>
+#endif
+#include <fcntl.h>
+
+struct timeval now; // public wall clock that can jump around
+struct timeval tick; // monotonic clock
+
+/*
+ * Calculate difference from now time to target time in milliseconds.
+ */
+int tv_timerdelta_millis(struct timeval *_now, struct timeval *_target)
+{
+ int deltasec = _target->tv_sec - _now->tv_sec;
+ int deltausec = _target->tv_usec - _now->tv_usec;
+ while (deltausec < 0) {
+ deltausec += 1000000;
+ --deltasec;
+ }
+ return deltasec * 1000 + deltausec / 1000;
+}
+
+/*
+ * Add milliseconds to input parameter a returning
+ * the result though parameter ret.
+ */
+void tv_timeradd_millis(struct timeval *ret, struct timeval *a, int millis)
+{
+ if (ret != a) {
+ // Copy if different pointers..
+ *ret = *a;
+ }
+ int usec = (int)(ret->tv_usec) + millis * 1000;
+ if (usec >= 1000000) {
+ int dsec = (usec / 1000000);
+ ret->tv_sec += dsec;
+ usec %= 1000000;
+ // if (debug>3) printf("tv_timeadd_millis() dsec=%d dusec=%d\n",dsec, usec);
+ }
+ ret->tv_usec = usec;
+}
+
+/*
+ * Add seconds to input parameter a returning
+ * the result though parameter ret.
+ */
+void tv_timeradd_seconds(struct timeval *ret, struct timeval *a, int seconds)
+{
+ if (ret != a) {
+ // Copy if different pointers..
+ *ret = *a;
+ }
+ ret->tv_sec += seconds;
+}
+
+
+/*
+ * Comparison returning -1/0/+1 depending on ( a <=> b )
+ *
+ * This handles overflow wraparound of Y2038 issue of 32-bit UNIX time_t.
+ */
+int timecmp(const time_t a, const time_t b)
+{
+ const int i = (int)(a - b);
+ if (i == 0) return 0;
+ if (i > 0) return 1;
+ return -1;
+}
+
+/*
+ * Time compare function returning -1/0/+1 depending
+ * which parameter presents time before the other.
+ * Zero means equals.
+ */
+int tv_timercmp(struct timeval * const a, struct timeval * const b)
+{
+ // if (debug>3) {
+ // int dt_sec = a->tv_sec - b->tv_sec;
+ // int dt_usec = a->tv_usec - b->tv_usec;
+ // printf("tv_timercmp(%d.%06d <=> %d.%06d) dt=%d:%06d ret= ",
+ // a->tv_sec, a->tv_usec, b->tv_sec, b->tv_usec, dt_sec, dt_usec);
+ // }
+
+ // Time delta calculation to avoid year 2038 issue
+ const int dt = timecmp(a->tv_sec, b->tv_sec);
+ if (dt != 0) {
+ // if (debug>3) printf("%ds\n", dt);
+ return dt;
+ }
+ // tv_usec is always in range 0 .. 999 999
+ if (a->tv_usec < b->tv_usec) {
+ // if (debug>3) printf("-1u\n");
+ return -1;
+ }
+ if (a->tv_usec > b->tv_usec) {
+ // if (debug>3) printf("1u\n");
+ return 1;
+ }
+ // if (debug>3) printf("0\n");
+ return 0; // equals!
+}
+
+/*
+ * Compare *tv with current time value (now), and if the difference
+ * is more than margin seconds, then call resetfunc with resetarg.
+ *
+ * Usually resetarg == tv, but not always.
+ * See
+ */
+void tv_timerbounds(const char *timername,
+ struct timeval *tv,
+ const int margin,
+ void (*resetfunc)(void*),
+ void *resetarg)
+{
+ // Check that system time has not jumped too far ahead/back;
+ // that it is within margin seconds to tv.
+
+ struct timeval nowminus;
+ struct timeval nowplus;
+
+ tv_timeradd_seconds(&nowminus, &tick, -margin);
+
+ // If current time MINUS margin is AFTER tv, then reset.
+ if (tv_timercmp(tv, &nowminus) < 0) {
+ if (debug)
+ printf("System time has gone too much forwards, Resetting timer '%s'. dt=%d margin=%d\n",
+ timername, (int)(tv->tv_sec - nowminus.tv_sec), margin);
+ resetfunc(resetarg);
+ }
+
+ tv_timeradd_seconds(&nowplus, &tick, margin);
+
+ // If current time PLUS margin is BEFORE tv, then reset.
+ if (tv_timercmp(&nowplus, tv) < 0) {
+ if (debug)
+ printf("System time has gone too much backwards, Resetting timer '%s'. dt=%d margin=%d\n",
+ timername, (int)(nowplus.tv_sec - tv->tv_sec), margin);
+ resetfunc(resetarg);
+ }
+}
diff --git a/timestamp.c b/timestamp.c
new file mode 100644
index 0000000..eb4e50d
--- /dev/null
+++ b/timestamp.c
@@ -0,0 +1,184 @@
+#include "aprx.h"
+
+/* Time Base Conversion Macros
+ *
+ * The NTP timebase is 00:00 Jan 1 1900. The local
+ * time base is 00:00 Jan 1 1970. Convert between
+ * these two by added or substracting 70 years
+ * worth of time. Note that 17 of these years were
+ * leap years.
+ */
+#define TIME_BASEDIFF (((70U*365U + 17U) * 24U*3600U))
+#define TIME_NTP_TO_LOCAL(t) ((t)-TIME_BASEDIFF)
+#define TIME_LOCAL_TO_NTP(t) ((t)+TIME_BASEDIFF)
+
+typedef struct ntptime {
+ uint32_t seconds;
+ uint32_t fraction;
+} ntptime_t;
+
+uint64_t unix_tv_to_ntp(struct timeval *tv) {
+ // Reciprocal conversion of tv_usec to fractional NTP seconds
+ // Multiply tv_usec by (2^64)/1_000_000
+ // GCC optimized this nicely on i386
+ uint64_t fract = 18446744073709ULL * (uint32_t)(tv->tv_usec);
+ // Scale it back by 32 bit positions
+ fract >>= 32;
+ // Straight-forward conversion of tv_sec to NTP seconds
+ uint64_t ntptime = TIME_LOCAL_TO_NTP(tv->tv_sec);
+ ntptime <<= 32;
+ return ntptime + fract;
+}
+
+void unix_tv_to_ntp4(struct timeval *tv, ntptime_t *ntp) {
+ // Reciprocal conversion of tv_usec to fractional NTP seconds
+ // Multiply tv_usec by ((2^64)/1_000_000) / (2^32)
+ // GCC optimized this nicely on i386, and 64-bit machines
+ uint32_t fract = (18446744073709ULL * (uint32_t)(tv->tv_usec)) >> 32;
+ //
+ // movl 4(%ebx), %eax
+ // imull $4294, %eax, %esi ;; 32*32->32 --> %esi
+ // movl $-140462611, %edi
+ // mull %edi ;; 32*32->64 --> %edx:eax
+ // addl %edx, %esi ;; sum %esi + %edx
+ //
+ ntp->fraction = fract;
+ // Straight-forward conversion of tv_sec to NTP seconds
+ ntp->seconds = TIME_LOCAL_TO_NTP(tv->tv_sec);
+}
+
+void unix_tv_to_ntp4a(struct timeval *tv, ntptime_t *ntp) {
+ // Reciprocal conversion of tv_usec to fractional NTP seconds
+ // Multiply tv_usec by ((2^64)/1_000_000) / (2^32)
+ // GCC optimizes this slightly better for ARM, than ntp4()
+ // .. for i386 ntp4() and ntp4a() are equal.
+ uint64_t fract = 18446744073709ULL * (uint32_t)(tv->tv_usec);
+ // Scale it back by 32 bit positions
+ fract >>= 32;
+ ntp->fraction = (uint32_t)fract;
+ // Straight-forward conversion of tv_sec to NTP seconds
+ ntp->seconds = TIME_LOCAL_TO_NTP(tv->tv_sec);
+}
+
+
+uint64_t unix_tv_to_ntp2(struct timeval *tv) {
+ uint64_t tt = TIME_LOCAL_TO_NTP(tv->tv_sec);
+ tt <<= 32;
+ uint64_t tu = tv->tv_usec;
+ tu <<= 32;
+ // Following causes gcc to call __udivdi3()
+ // on 32-bit machines
+ tu /= 1000000; // Fixed point scaling..
+ return (tt + tu);
+}
+
+// static const double usec2NtpFract = 4294.9672960D; // 2^32 / 1E6
+
+uint64_t unix_tv_to_ntp3(struct timeval *tv) {
+ uint64_t tt = TIME_LOCAL_TO_NTP(tv->tv_sec);
+ tt <<= 32;
+// FP math is bad on embedded systems...
+// double fract = usec2NtpFract * (uint32_t)tv->tv_usec;
+// tt += (int64_t)fract;
+ return tt;
+}
+
+
+static const char *BASE64EncodingDictionary =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789"
+ "+/";
+
+void encode_aprsis_ntptimestamp(uint64_t ntptime, char timestamp[8])
+{
+ int i;
+
+ ntptime >>= 22; // scale to 1/1024 seconds
+ for (i = 6; i >= 0; --i) {
+ int n = (((int)ntptime) & 0x3F); // lowest 6 bits
+ // printf(" [n=%d]\n", n);
+ ntptime >>= 6;
+ timestamp[i] = BASE64EncodingDictionary[n];
+ }
+ timestamp[7] = 0;
+}
+
+static const int8_t BASE64DecodingDictionary[128] =
+ { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, // ' ', '!', '"', '#'
+ -1, -1, -1, -1, // '$', '%', '&'', '\''
+ -1, -1, -1, 62, // '(', ')', '*', '+',
+ -1, -1, -1, 63, // ',', '-', '.', '/'
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // '0' .. '9'
+ -1, -1, -1, -1, -1, -1, // ':', ';', '<', '=', '>', '?'
+ -1, 0, 1, 2, 3, 4, 5, 6, // '@', 'A' .. 'G'
+ 7, 8, 9, 10, 11, 12, 13, 14, // 'H' .. 'O'
+ 15, 16, 17, 18, 19, 20, 21, 22, // 'P' .. 'W'
+ 23, 24, 25, -1, -1, -1, -1, -1, // 'X'..'Z', '[', '\\', ']', '^', '_'
+ -1, 26, 27, 28, 29, 30, 31, 32, // '`', 'a' .. 'g'
+ 33, 34, 35, 36, 37, 38, 39, 40, // 'h' .. 'o'
+ 41, 42, 43, 44, 45, 46, 47, 48, // 'p' .. 'w'
+ 49, 50, 51, -1, -1, -1, -1, -1 }; // 'x'..'z', ...
+
+
+int decode_aprsis_ntptimestamp(char timestamp[8], uint64_t *ntptimep)
+{
+ uint64_t ntptime = 0;
+
+ int i, n;
+ char c;
+
+ for (i = 0; i < 7; ++i) {
+ c = timestamp[i];
+ if (c <= 0 || c > 127) return -1; // BARF!
+ n = BASE64DecodingDictionary[(int)c];
+ // printf(" [n=%d]\n", n);
+ if (n < 0) {
+ // Should not happen!
+ return -1; // Decode fail!
+ }
+
+ ntptime <<= 6;
+ ntptime |= n;
+ }
+ ntptime <<= 22;
+ *ntptimep = ntptime;
+ return 0; // Decode OK
+}
+
+#ifdef TESTING
+
+int main(int argc, char *argv[]) {
+
+ struct timeval tv;
+ char timestamp[8];
+ uint64_t ntptime;
+ ntptime_t ntp_time;
+
+ // gettimeofday(&tv, NULL);
+
+ // Example time.. (refvalue: NTPseconds!)
+ tv.tv_sec = TIME_NTP_TO_LOCAL(3484745636U); tv.tv_usec = 709603U;
+
+ ntptime = unix_tv_to_ntp(&tv);
+ printf("NTPtime1 = %08x.%08x \n", (uint32_t)(ntptime >> 32), (uint32_t)ntptime);
+ ntptime = unix_tv_to_ntp2(&tv);
+ printf("NTPtime2 = %08x.%08x \n", (uint32_t)(ntptime >> 32), (uint32_t)ntptime);
+ // ntptime = unix_tv_to_ntp3(&tv);
+ // printf("NTPtime3 = %08x.%08x \n", (uint32_t)(ntptime >> 32), (uint32_t)ntptime);
+
+ unix_tv_to_ntp4(&tv, &ntp_time);
+ printf("NTPtime4 = %08x.%08x \n", ntp_time.seconds, ntp_time.fraction);
+
+ encode_aprsis_ntptimestamp( ntptime, timestamp );
+ printf("Timestamp = %s\n", timestamp);
+
+ int rc = decode_aprsis_ntptimestamp( timestamp, &ntptime );
+ printf("Decode rc=%d\n", rc);
+ printf("NTPtime = %08x.%08x \n", (uint32_t)(ntptime >> 32), (uint32_t)ntptime);
+
+ return 0;
+}
+#endif
diff --git a/tt.5383 b/tt.5383
new file mode 100644
index 0000000..06bfe3b
--- /dev/null
+++ b/tt.5383
@@ -0,0 +1,3101 @@
+execve("/bin/svn", ["svn", "--username", "oh2mqk", "commit", "doc/", "-m", "Add note about USB serial ports "...], [/* 59 vars */]) = 0
+brk(0) = 0x7fb0c4d8b000
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41f5000
+access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
+open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
+fstat(3, {st_mode=S_IFREG|0644, st_size=212134, ...}) = 0
+mmap(NULL, 212134, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fb0c41c1000
+close(3) = 0
+open("/lib64/libsvn_client-1.so.0", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\340\0a\2249\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=421472, ...}) = 0
+mmap(NULL, 2508008, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c3d72000
+mprotect(0x7fb0c3dd5000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0c3fd4000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x62000) = 0x7fb0c3fd4000
+close(3) = 0
+open("/lib64/libsvn_wc-1.so.0", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0Z\301f8\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=741088, ...}) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41c0000
+mmap(NULL, 2828136, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c3abf000
+mprotect(0x7fb0c3b6f000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0c3d6e000, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xaf000) = 0x7fb0c3d6e000
+close(3) = 0
+open("/lib64/libsvn_ra-1.so.0", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260@ \2259\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=61752, ...}) = 0
+mmap(NULL, 2151568, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c38b1000
+mprotect(0x7fb0c38be000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0c3abd000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xc000) = 0x7fb0c3abd000
+close(3) = 0
+open("/lib64/libsvn_diff-1.so.0", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\340=\200f8\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=89768, ...}) = 0
+mmap(NULL, 2180064, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c369c000
+mprotect(0x7fb0c36b0000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0c38af000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x13000) = 0x7fb0c38af000
+close(3) = 0
+open("/lib64/libsvn_ra_local-1.so.0", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0p4\240l8\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=44720, ...}) = 0
+mmap(NULL, 2134952, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c3492000
+mprotect(0x7fb0c349a000, 2097152, PROT_NONE) = 0
+mmap(0x7fb0c369a000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x8000) = 0x7fb0c369a000
+close(3) = 0
+open("/lib64/libsvn_repos-1.so.0", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0 \227\300h8\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=223680, ...}) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41bf000
+mmap(NULL, 2313200, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c325d000
+mprotect(0x7fb0c3290000, 2097152, PROT_NONE) = 0
+mmap(0x7fb0c3490000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x33000) = 0x7fb0c3490000
+close(3) = 0
+open("/lib64/libsvn_fs-1.so.0", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0p:\0h8\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=48168, ...}) = 0
+mmap(NULL, 2139032, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c3052000
+mprotect(0x7fb0c305c000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0c325b000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x9000) = 0x7fb0c325b000
+close(3) = 0
+open("/lib64/libsvn_fs_fs-1.so.0", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20\240 at g8\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=233360, ...}) = 0
+mmap(NULL, 2322088, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c2e1b000
+mprotect(0x7fb0c2e51000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0c3050000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x35000) = 0x7fb0c3050000
+close(3) = 0
+open("/lib64/libsvn_fs_base-1.so.0", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\360\226 m8\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=204032, ...}) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41be000
+mmap(NULL, 2293192, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c2beb000
+mprotect(0x7fb0c2c1a000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0c2e19000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2e000) = 0x7fb0c2e19000
+close(3) = 0
+open("/lib64/libsvn_fs_util-1.so.0", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\200\f\0g8\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=14064, ...}) = 0
+mmap(NULL, 2105520, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c29e8000
+mprotect(0x7fb0c29ea000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0c2be9000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1000) = 0x7fb0c2be9000
+close(3) = 0
+open("/lib64/libsvn_ra_svn-1.so.0", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\360u at k8\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=132640, ...}) = 0
+mmap(NULL, 2222232, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c27c9000
+mprotect(0x7fb0c27e7000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0c29e6000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1d000) = 0x7fb0c29e6000
+close(3) = 0
+open("/lib64/libsasl2.so.3", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`K\30001\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=122848, ...}) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41bd000
+mmap(NULL, 2213960, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c25ac000
+mprotect(0x7fb0c25c8000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0c27c7000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b000) = 0x7fb0c27c7000
+close(3) = 0
+open("/lib64/libsvn_ra_serf-1.so.0", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0000\225`\2239\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=203848, ...}) = 0
+mmap(NULL, 2292144, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c237c000
+mprotect(0x7fb0c23a8000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0c25a7000, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2b000) = 0x7fb0c25a7000
+close(3) = 0
+open("/lib64/libserf-1.so.0", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P{\240\2239\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=115968, ...}) = 0
+mmap(NULL, 2205248, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c2161000
+mprotect(0x7fb0c217b000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0c237a000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19000) = 0x7fb0c237a000
+close(3) = 0
+open("/lib64/libsvn_delta-1.so.0", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\300<\340k8\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=85944, ...}) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41bc000
+mmap(NULL, 2176072, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c1f4d000
+mprotect(0x7fb0c1f5f000, 2097152, PROT_NONE) = 0
+mmap(0x7fb0c215f000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x12000) = 0x7fb0c215f000
+close(3) = 0
+open("/lib64/libsvn_subr-1.so.0", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0000\266\341l8\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=523360, ...}) = 0
+mmap(NULL, 2611640, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c1ccf000
+mprotect(0x7fb0c1d48000, 2097152, PROT_NONE) = 0
+mmap(0x7fb0c1f48000, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x79000) = 0x7fb0c1f48000
+close(3) = 0
+open("/lib64/libz.so.1", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20\"@\r1\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=92560, ...}) = 0
+mmap(NULL, 2183688, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c1ab9000
+mprotect(0x7fb0c1ace000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0c1ccd000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x14000) = 0x7fb0c1ccd000
+close(3) = 0
+open("/lib64/libsqlite3.so.0", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0000\260\0k8\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=792288, ...}) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41bb000
+mmap(NULL, 2873304, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c17fb000
+mprotect(0x7fb0c18b4000, 2097152, PROT_NONE) = 0
+mmap(0x7fb0c1ab4000, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xb9000) = 0x7fb0c1ab4000
+close(3) = 0
+open("/lib64/libmagic.so.1", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`D\340\0321\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=126128, ...}) = 0
+mmap(NULL, 2217848, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c15dd000
+mprotect(0x7fb0c15f9000, 2097152, PROT_NONE) = 0
+mmap(0x7fb0c17f9000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c000) = 0x7fb0c17f9000
+close(3) = 0
+open("/lib64/libaprutil-1.so.0", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0@\232\340\0351\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=173416, ...}) = 0
+mmap(NULL, 2263848, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c13b4000
+mprotect(0x7fb0c13dc000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0c15db000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x27000) = 0x7fb0c15db000
+close(3) = 0
+open("/lib64/libcrypt.so.1", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\320\16\200(1\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=43848, ...}) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41ba000
+mmap(NULL, 2318912, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c117d000
+mprotect(0x7fb0c1185000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0c1384000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x7000) = 0x7fb0c1384000
+mmap(0x7fb0c1386000, 184896, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fb0c1386000
+close(3) = 0
+open("/lib64/libexpat.so.1", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0>@\0221\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=175384, ...}) = 0
+mmap(NULL, 2265312, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c0f53000
+mprotect(0x7fb0c0f7a000, 2097152, PROT_NONE) = 0
+mmap(0x7fb0c117a000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x27000) = 0x7fb0c117a000
+close(3) = 0
+open("/lib64/libdb-5.3.so", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\220\362\"\0341\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=1840560, ...}) = 0
+mmap(NULL, 3927304, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c0b94000
+mprotect(0x7fb0c0d49000, 2097152, PROT_NONE) = 0
+mmap(0x7fb0c0f49000, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b5000) = 0x7fb0c0f49000
+close(3) = 0
+open("/lib64/libapr-1.so.0", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0 \316\240\0361\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=219224, ...}) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41b9000
+mmap(NULL, 2309880, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c0960000
+mprotect(0x7fb0c0992000, 2097152, PROT_NONE) = 0
+mmap(0x7fb0c0b92000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x32000) = 0x7fb0c0b92000
+close(3) = 0
+open("/lib64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\340m\0\r1\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=150800, ...}) = 0
+mmap(NULL, 2213104, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c0743000
+mprotect(0x7fb0c075b000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0c095a000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x17000) = 0x7fb0c095a000
+mmap(0x7fb0c095c000, 13552, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fb0c095c000
+close(3) = 0
+open("/lib64/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\320\16\300\f1\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=22440, ...}) = 0
+mmap(NULL, 2109744, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c053f000
+mprotect(0x7fb0c0542000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0c0741000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2000) = 0x7fb0c0741000
+close(3) = 0
+open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0p\36B\f1\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=2100672, ...}) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41b8000
+mmap(NULL, 3924576, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0c0180000
+mprotect(0x7fb0c0334000, 2097152, PROT_NONE) = 0
+mmap(0x7fb0c0534000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b4000) = 0x7fb0c0534000
+mmap(0x7fb0c053a000, 16992, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fb0c053a000
+close(3) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41b7000
+open("/lib64/libresolv.so.2", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0@:\300\0161\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=113808, ...}) = 0
+mmap(NULL, 2202264, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0bff66000
+mprotect(0x7fb0bff7c000, 2097152, PROT_NONE) = 0
+mmap(0x7fb0c017c000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16000) = 0x7fb0c017c000
+mmap(0x7fb0c017e000, 6808, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fb0c017e000
+close(3) = 0
+open("/usr/lib64/tls/x86_64/libssl.so.10", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+stat("/usr/lib64/tls/x86_64", 0x7fff60105170) = -1 ENOENT (No such file or directory)
+open("/usr/lib64/tls/libssl.so.10", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+stat("/usr/lib64/tls", {st_mode=S_IFDIR|0555, st_size=4096, ...}) = 0
+open("/usr/lib64/x86_64/libssl.so.10", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+stat("/usr/lib64/x86_64", 0x7fff60105170) = -1 ENOENT (No such file or directory)
+open("/usr/lib64/libssl.so.10", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\300\203\1\0271\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=446080, ...}) = 0
+mmap(NULL, 2536528, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0bfcfa000
+mprotect(0x7fb0bfd5c000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0bff5b000, 45056, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x61000) = 0x7fb0bff5b000
+close(3) = 0
+open("/usr/lib64/tls/libcrypto.so.10", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+open("/usr/lib64/libcrypto.so.10", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0@\234F\0251\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=1993248, ...}) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41b6000
+mmap(NULL, 4091768, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0bf913000
+mprotect(0x7fb0bfad0000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0bfccf000, 159744, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1bc000) = 0x7fb0bfccf000
+mmap(0x7fb0bfcf6000, 16248, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fb0bfcf6000
+close(3) = 0
+open("/usr/lib64/tls/libldap_r-2.4.so.2", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+open("/usr/lib64/libldap_r-2.4.so.2", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\200\23\241\2229\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=365848, ...}) = 0
+mmap(NULL, 2465032, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0bf6b9000
+mprotect(0x7fb0bf70e000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0bf90d000, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x54000) = 0x7fb0bf90d000
+mmap(0x7fb0bf911000, 7432, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fb0bf911000
+close(3) = 0
+open("/usr/lib64/tls/liblber-2.4.so.2", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+open("/usr/lib64/liblber-2.4.so.2", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\3206\30011\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=64280, ...}) = 0
+mmap(NULL, 2155848, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0bf4aa000
+mprotect(0x7fb0bf4b8000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0bf6b7000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xd000) = 0x7fb0bf6b7000
+close(3) = 0
+open("/usr/lib64/tls/libgssapi_krb5.so.2", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+open("/usr/lib64/libgssapi_krb5.so.2", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20\275@\0261\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=310624, ...}) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41b5000
+mmap(NULL, 2398304, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0bf260000
+mprotect(0x7fb0bf2a7000, 2097152, PROT_NONE) = 0
+mmap(0x7fb0bf4a7000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x47000) = 0x7fb0bf4a7000
+close(3) = 0
+open("/usr/lib64/tls/libkrb5.so.3", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+open("/usr/lib64/libkrb5.so.3", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\240Q\202\0261\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=929904, ...}) = 0
+mmap(NULL, 3012704, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0bef80000
+mprotect(0x7fb0bf050000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0bf24f000, 69632, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xcf000) = 0x7fb0bf24f000
+close(3) = 0
+open("/usr/lib64/tls/libk5crypto.so.3", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+open("/usr/lib64/libk5crypto.so.3", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260H\0\0261\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=217248, ...}) = 0
+mmap(NULL, 2310640, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0bed4b000
+mprotect(0x7fb0bed7d000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0bef7c000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x31000) = 0x7fb0bef7c000
+mmap(0x7fb0bef7f000, 496, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fb0bef7f000
+close(3) = 0
+open("/usr/lib64/tls/libcom_err.so.2", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+open("/usr/lib64/libcom_err.so.2", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0p\25\0\0251\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=18320, ...}) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41b4000
+mmap(NULL, 2109928, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0beb47000
+mprotect(0x7fb0beb4a000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0bed49000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2000) = 0x7fb0bed49000
+close(3) = 0
+open("/lib64/libuuid.so.1", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20\25\300\0201\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=22624, ...}) = 0
+mmap(NULL, 2113920, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0be942000
+mprotect(0x7fb0be946000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0beb45000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3000) = 0x7fb0beb45000
+close(3) = 0
+open("/lib64/libfreebl3.so", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\200>\200'1\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=517384, ...}) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41b3000
+mmap(NULL, 2619232, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0be6c2000
+mprotect(0x7fb0be73c000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0be93b000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x79000) = 0x7fb0be93b000
+mmap(0x7fb0be93e000, 14176, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fb0be93e000
+close(3) = 0
+open("/usr/lib64/tls/libssl3.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+open("/usr/lib64/libssl3.so", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\220\260`\2219\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=262984, ...}) = 0
+mmap(NULL, 2351976, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0be483000
+mprotect(0x7fb0be4be000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0be6bd000, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3a000) = 0x7fb0be6bd000
+mmap(0x7fb0be6c1000, 872, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fb0be6c1000
+close(3) = 0
+open("/usr/lib64/tls/libsmime3.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+open("/usr/lib64/libsmime3.so", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`\236\240\2219\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=192376, ...}) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41b2000
+mmap(NULL, 2280576, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0be256000
+mprotect(0x7fb0be27f000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0be47e000, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x7fb0be47e000
+close(3) = 0
+open("/usr/lib64/tls/libnss3.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+open("/usr/lib64/libnss3.so", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\232\341\2209\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=1360352, ...}) = 0
+mmap(NULL, 3434440, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0bdf0f000
+mprotect(0x7fb0be04e000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0be24d000, 32768, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x13e000) = 0x7fb0be24d000
+mmap(0x7fb0be255000, 1992, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fb0be255000
+close(3) = 0
+open("/usr/lib64/tls/libnssutil3.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+open("/usr/lib64/libnssutil3.so", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20\276 \2219\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=184296, ...}) = 0
+mmap(NULL, 2275936, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0bdce3000
+mprotect(0x7fb0bdd08000, 2097152, PROT_NONE) = 0
+mmap(0x7fb0bdf08000, 28672, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x25000) = 0x7fb0bdf08000
+close(3) = 0
+open("/usr/lib64/tls/libplds4.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+open("/usr/lib64/libplds4.so", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\220\20`\2209\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=18168, ...}) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41b1000
+mmap(NULL, 2109800, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0bdadf000
+mprotect(0x7fb0bdae2000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0bdce1000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2000) = 0x7fb0bdce1000
+close(3) = 0
+open("/usr/lib64/tls/libplc4.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+open("/usr/lib64/libplc4.so", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20\25\240\2209\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=22272, ...}) = 0
+mmap(NULL, 2113936, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0bd8da000
+mprotect(0x7fb0bd8de000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0bdadd000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3000) = 0x7fb0bdadd000
+close(3) = 0
+open("/usr/lib64/tls/libnspr4.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+open("/usr/lib64/libnspr4.so", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\340\321 \2209\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=251552, ...}) = 0
+mmap(NULL, 2350496, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0bd69c000
+mprotect(0x7fb0bd6d6000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0bd8d5000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x39000) = 0x7fb0bd8d5000
+mmap(0x7fb0bd8d8000, 7584, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fb0bd8d8000
+close(3) = 0
+open("/usr/lib64/tls/libkrb5support.so.0", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+open("/usr/lib64/libkrb5support.so.0", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0 6\300\0261\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=60896, ...}) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41b0000
+mmap(NULL, 2152008, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0bd48e000
+mprotect(0x7fb0bd49b000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0bd69a000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xc000) = 0x7fb0bd69a000
+close(3) = 0
+open("/usr/lib64/tls/libkeyutils.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+open("/usr/lib64/libkeyutils.so.1", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0p\25\200\0251\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=17984, ...}) = 0
+mmap(NULL, 2109712, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0bd28a000
+mprotect(0x7fb0bd28d000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0bd48c000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2000) = 0x7fb0bd48c000
+close(3) = 0
+open("/usr/lib64/tls/librt.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+open("/usr/lib64/librt.so.1", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\320\"\200\0161\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=47400, ...}) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41af000
+mmap(NULL, 2128952, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0bd082000
+mprotect(0x7fb0bd089000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0bd288000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x6000) = 0x7fb0bd288000
+close(3) = 0
+open("/usr/lib64/tls/libselinux.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+open("/usr/lib64/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\240d@\0161\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=144952, ...}) = 0
+mmap(NULL, 2242712, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0bce5e000
+mprotect(0x7fb0bce7f000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0bd07e000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x20000) = 0x7fb0bd07e000
+mmap(0x7fb0bd080000, 6296, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fb0bd080000
+close(3) = 0
+open("/usr/lib64/tls/libpcre.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+open("/usr/lib64/libpcre.so.1", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0p\27\0\0161\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=421144, ...}) = 0
+mmap(NULL, 2511368, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0bcbf8000
+mprotect(0x7fb0bcc5d000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0bce5c000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x64000) = 0x7fb0bce5c000
+close(3) = 0
+open("/usr/lib64/tls/liblzma.so.5", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+open("/usr/lib64/liblzma.so.5", O_RDONLY|O_CLOEXEC) = 3
+read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\3400\300\r1\0\0\0"..., 832) = 832
+fstat(3, {st_mode=S_IFREG|0755, st_size=155400, ...}) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41ae000
+mmap(NULL, 2245240, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb0bc9d3000
+mprotect(0x7fb0bc9f7000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0bcbf6000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x23000) = 0x7fb0bcbf6000
+close(3) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41ad000
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41ac000
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41ab000
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41aa000
+mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41a8000
+arch_prctl(ARCH_SET_FS, 0x7fb0c41a8880) = 0
+mprotect(0x7fb0c0534000, 16384, PROT_READ) = 0
+mprotect(0x7fb0c095a000, 4096, PROT_READ) = 0
+mprotect(0x7fb0bcbf6000, 4096, PROT_READ) = 0
+mprotect(0x7fb0bce5c000, 4096, PROT_READ) = 0
+mprotect(0x7fb0c0741000, 4096, PROT_READ) = 0
+mprotect(0x7fb0bd07e000, 4096, PROT_READ) = 0
+mprotect(0x7fb0bd288000, 4096, PROT_READ) = 0
+mprotect(0x7fb0bd48c000, 4096, PROT_READ) = 0
+mprotect(0x7fb0c017c000, 4096, PROT_READ) = 0
+mprotect(0x7fb0bd69a000, 4096, PROT_READ) = 0
+mprotect(0x7fb0bd8d5000, 4096, PROT_READ) = 0
+mprotect(0x7fb0bdadd000, 4096, PROT_READ) = 0
+mprotect(0x7fb0bdce1000, 4096, PROT_READ) = 0
+mprotect(0x7fb0bdf08000, 24576, PROT_READ) = 0
+mprotect(0x7fb0be24d000, 20480, PROT_READ) = 0
+mprotect(0x7fb0be47e000, 16384, PROT_READ) = 0
+mprotect(0x7fb0c1ccd000, 4096, PROT_READ) = 0
+mprotect(0x7fb0be6bd000, 12288, PROT_READ) = 0
+mprotect(0x7fb0be93b000, 8192, PROT_READ) = 0
+mprotect(0x7fb0beb45000, 4096, PROT_READ) = 0
+mprotect(0x7fb0bed49000, 4096, PROT_READ) = 0
+mprotect(0x7fb0bef7c000, 8192, PROT_READ) = 0
+mprotect(0x7fb0bf24f000, 57344, PROT_READ) = 0
+mprotect(0x7fb0bf4a7000, 4096, PROT_READ) = 0
+mprotect(0x7fb0bf6b7000, 4096, PROT_READ) = 0
+mprotect(0x7fb0c1384000, 4096, PROT_READ) = 0
+mprotect(0x7fb0c27c7000, 4096, PROT_READ) = 0
+mprotect(0x7fb0bf90d000, 8192, PROT_READ) = 0
+mprotect(0x7fb0bfccf000, 110592, PROT_READ) = 0
+mprotect(0x7fb0bff5b000, 16384, PROT_READ) = 0
+mprotect(0x7fb0c0b92000, 4096, PROT_READ) = 0
+mprotect(0x7fb0c0f49000, 28672, PROT_READ) = 0
+mprotect(0x7fb0c117a000, 8192, PROT_READ) = 0
+mprotect(0x7fb0c15db000, 4096, PROT_READ) = 0
+mprotect(0x7fb0c17f9000, 4096, PROT_READ) = 0
+mprotect(0x7fb0c1ab4000, 8192, PROT_READ) = 0
+mprotect(0x7fb0c1f48000, 12288, PROT_READ) = 0
+mprotect(0x7fb0c215f000, 4096, PROT_READ) = 0
+mprotect(0x7fb0c237a000, 4096, PROT_READ) = 0
+mprotect(0x7fb0c25a7000, 16384, PROT_READ) = 0
+mprotect(0x7fb0c29e6000, 4096, PROT_READ) = 0
+mprotect(0x7fb0c2be9000, 4096, PROT_READ) = 0
+mprotect(0x7fb0c2e19000, 4096, PROT_READ) = 0
+mprotect(0x7fb0c3050000, 4096, PROT_READ) = 0
+mprotect(0x7fb0c325b000, 4096, PROT_READ) = 0
+mprotect(0x7fb0c3490000, 4096, PROT_READ) = 0
+mprotect(0x7fb0c369a000, 4096, PROT_READ) = 0
+mprotect(0x7fb0c38af000, 4096, PROT_READ) = 0
+mprotect(0x7fb0c3abd000, 4096, PROT_READ) = 0
+mprotect(0x7fb0c3d6e000, 8192, PROT_READ) = 0
+mprotect(0x7fb0c3fd4000, 4096, PROT_READ) = 0
+mprotect(0x7fb0c4431000, 49152, PROT_READ) = 0
+mprotect(0x7fb0c41f6000, 4096, PROT_READ) = 0
+munmap(0x7fb0c41c1000, 212134) = 0
+set_tid_address(0x7fb0c41a8b50) = 5383
+set_robust_list(0x7fb0c41a8b60, 24) = 0
+rt_sigaction(SIGRTMIN, {0x7fb0c07498c0, [], SA_RESTORER|SA_SIGINFO, 0x7fb0c0752750}, NULL, 8) = 0
+rt_sigaction(SIGRT_1, {0x7fb0c0749950, [], SA_RESTORER|SA_RESTART|SA_SIGINFO, 0x7fb0c0752750}, NULL, 8) = 0
+rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0
+getrlimit(RLIMIT_STACK, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
+statfs("/sys/fs/selinux", 0x7fff60106060) = -1 ENOENT (No such file or directory)
+statfs("/selinux", 0x7fff60106060) = -1 ENOENT (No such file or directory)
+brk(0) = 0x7fb0c4d8b000
+brk(0x7fb0c4dac000) = 0x7fb0c4dac000
+open("/proc/filesystems", O_RDONLY) = 3
+fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41f4000
+read(3, "nodev\tsysfs\nnodev\trootfs\nnodev\tr"..., 1024) = 384
+read(3, "", 1024) = 0
+close(3) = 0
+munmap(0x7fb0c41f4000, 4096) = 0
+access("/etc/system-fips", F_OK) = -1 ENOENT (No such file or directory)
+fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
+fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
+fstat(2, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
+open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
+fstat(3, {st_mode=S_IFREG|0644, st_size=106070960, ...}) = 0
+mmap(NULL, 106070960, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fb0b64aa000
+close(3) = 0
+open("/usr/share/locale/locale.alias", O_RDONLY|O_CLOEXEC) = 3
+fstat(3, {st_mode=S_IFREG|0644, st_size=2492, ...}) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41f4000
+read(3, "# Locale name alias data base.\n#"..., 4096) = 2492
+read(3, "", 4096) = 0
+close(3) = 0
+munmap(0x7fb0c41f4000, 4096) = 0
+open("/usr/lib/locale/A4/LC_PAPER", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+ioctl(0, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
+stat("/localhome/mea/.subversion", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
+lstat("/localhome/mea/.subversion/auth", {st_mode=S_IFDIR|0700, st_size=4096, ...}) = 0
+lstat("/localhome/mea/.subversion/auth/svn.simple", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
+lstat("/localhome/mea/.subversion/auth/svn.username", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
+lstat("/localhome/mea/.subversion/auth/svn.ssl.server", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
+lstat("/localhome/mea/.subversion/auth/svn.ssl.client-passphrase", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
+lstat("/localhome/mea/.subversion/README.txt", {st_mode=S_IFREG|0644, st_size=4277, ...}) = 0
+lstat("/localhome/mea/.subversion/servers", {st_mode=S_IFREG|0644, st_size=3270, ...}) = 0
+lstat("/localhome/mea/.subversion/config", {st_mode=S_IFREG|0664, st_size=4749, ...}) = 0
+open("/etc/subversion/servers", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+open("/localhome/mea/.subversion/servers", O_RDONLY|O_CLOEXEC) = 3
+fcntl(3, F_GETFD) = 0x1 (flags FD_CLOEXEC)
+brk(0) = 0x7fb0c4dac000
+brk(0x7fb0c4ddc000) = 0x7fb0c4ddc000
+read(3, "### This file specifies server-s"..., 4096) = 3270
+read(3, "", 4096) = 0
+read(3, "", 4096) = 0
+read(3, "", 4096) = 0
+close(3) = 0
+open("/etc/subversion/config", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
+open("/localhome/mea/.subversion/config", O_RDONLY|O_CLOEXEC) = 3
+read(3, "### This file configures various"..., 4096) = 4096
+read(3, " is:\n### file-name-pattern = p"..., 4096) = 653
+read(3, "", 4096) = 0
+read(3, "", 4096) = 0
+read(3, "", 4096) = 0
+close(3) = 0
+stat("Add note about USB serial ports benefitting of interface timeout parameter.", 0x7fff60105ca0) = -1 ENOENT (No such file or directory)
+getcwd("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk", 4096) = 77
+rt_sigaction(SIGINT, {0x7fb0c4216ad0, [], SA_RESTORER|SA_INTERRUPT, 0x7fb0c0752750}, {SIG_DFL, [], 0}, 8) = 0
+rt_sigaction(SIGHUP, {0x7fb0c4216ad0, [], SA_RESTORER|SA_INTERRUPT, 0x7fb0c0752750}, {SIG_DFL, [], 0}, 8) = 0
+rt_sigaction(SIGTERM, {0x7fb0c4216ad0, [], SA_RESTORER|SA_INTERRUPT, 0x7fb0c0752750}, {SIG_DFL, [], 0}, 8) = 0
+rt_sigaction(SIGPIPE, {SIG_IGN, [], SA_RESTORER|SA_INTERRUPT, 0x7fb0c0752750}, {SIG_DFL, [], 0}, 8) = 0
+rt_sigaction(SIGXFSZ, {SIG_IGN, [], SA_RESTORER|SA_INTERRUPT, 0x7fb0c0752750}, {SIG_DFL, [], 0}, 8) = 0
+getcwd("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk", 4096) = 77
+getcwd("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk", 4096) = 77
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/.svn", 0x7fff60105720) = -1 ENOENT (No such file or directory)
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn", {st_mode=S_IFDIR|0755, st_size=43, ...}) = 0
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db", {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+stat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db", {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+open("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db", O_RDWR|O_CLOEXEC) = 3
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+stat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db", {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 0, SEEK_SET) = 0
+read(3, "SQLite format 3\0\4\0\1\1\0@ \0\0\5\237\0\0\0\241"..., 100) = 100
+brk(0) = 0x7fb0c4ddc000
+brk(0x7fb0c4e09000) = 0x7fb0c4e09000
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 0, SEEK_SET) = 0
+read(3, "SQLite format 3\0\4\0\1\1\0@ \0\0\5\237\0\0\0\241"..., 1024) = 1024
+lseek(3, 11264, SEEK_SET) = 11264
+read(3, "\r\0\0\0\t\0x\0\0x\1\25\1J\1\234\1\342\2(\2\242\2\317\0030\0\0\0\0\0\0"..., 1024) = 1024
+lseek(3, 14336, SEEK_SET) = 14336
+read(3, "\r\3+\0\3\0\204\0\0\204\0\265\2\364\3+\3\227\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(3, 19456, SEEK_SET) = 19456
+read(3, "\r\0\0\0\5\0017\0\0017\2D\2m\2\354\3\321\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(3, 25600, SEEK_SET) = 25600
+read(3, "\r\3\230\0\2\0\233\0\0\233\3m\3\230\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(3, 27648, SEEK_SET) = 27648
+read(3, "\r\0\0\0\4\0\335\0\0\335\1\347\2L\3&\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(3, 28672, SEEK_SET) = 28672
+read(3, "\r\0\0\0\2\0\330\0\0\330\2'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(3, 33792, SEEK_SET) = 33792
+read(3, "\r\2\234\0\6\0E\0\2i\3\n\2\2\1\250\0\374\0E\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\237\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 8192, SEEK_SET) = 8192
+read(3, "\n\0\0\0\1\3\374\0\3\374\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\237\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\237\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 20480, SEEK_SET) = 20480
+read(3, "\r\0\0\0\0\4\0\0\2\230\2\230\2\230\2\230\2\230\2\230\2\230\2\230\2\230\1\232\1\232\1\232"..., 1024) = 1024
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\237\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24576, SEEK_SET) = 24576
+read(3, "\2\0\0\0\2\3\315\0\0\0\0y\3\342\3\315\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(3, 53248, SEEK_SET) = 53248
+read(3, "\n\3\226\0\35\0012\2\1d\1|\1\233\0012\1\304\1\333\1\356\2\0\2\33\1\260\2B\2M"..., 1024) = 1024
+lseek(3, 23552, SEEK_SET) = 23552
+read(3, "\5\3~\0$\3\25\f\0\0\0\241\3\265\3\260\3L\3X\3\246\3\241\3\234\3\227\3\222\3\210"..., 1024) = 1024
+lseek(3, 148480, SEEK_SET) = 148480
+read(3, "\r\0\0\0\2\2\202\0\2\202\3\17\3\17\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(3, 52224, SEEK_SET) = 52224
+read(3, "\n\2\240\0%\1+\0\1G\1O\1`\1o\1~\1\221\1\241\1\257\1\275\1\327\1\343\1\362"..., 1024) = 1024
+lseek(3, 163840, SEEK_SET) = 163840
+read(3, "\r\3(\0\2\1\301\0\1\301\2\244\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+getcwd("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk", 4096) = 77
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\237\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 22528, SEEK_SET) = 22528
+read(3, "\n\0\0\0\0\4\0\0\3\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741825, len=1}) = 0
+lseek(3, 21504, SEEK_SET) = 21504
+read(3, "\r\0\0\0\0\4\0\0\3\371\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+stat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db", {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+stat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db", {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+open("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", O_RDWR|O_CREAT|O_CLOEXEC, 0644) = 4
+fstat(4, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
+geteuid() = 530
+fstat(4, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
+open("/dev/urandom", O_RDONLY|O_CLOEXEC) = 5
+read(5, "!?\\6X$\260\376\v\3335\213\17H\267\303\0076eP\206\3N\264\320\311K\335\241vF\331"..., 256) = 256
+close(5) = 0
+lseek(4, 0, SEEK_SET) = 0
+write(4, "\331\325\5\371 \241c\327\377\377\377\377\224\337\311\2\0\0\0\241\0\0\2\0\0\0\4\0\0\0\0\0"..., 512) = 512
+lseek(4, 512, SEEK_SET) = 512
+write(4, "\0\0\0\27", 4) = 4
+lseek(4, 516, SEEK_SET) = 516
+write(4, "\n\0\0\0\0\4\0\0\3\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(4, 1540, SEEK_SET) = 1540
+write(4, "\224\337\311\2", 4) = 4
+lseek(4, 1544, SEEK_SET) = 1544
+write(4, "\0\0\0\26", 4) = 4
+lseek(4, 1548, SEEK_SET) = 1548
+write(4, "\r\0\0\0\0\4\0\0\3\371\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(4, 2572, SEEK_SET) = 2572
+write(4, "\224\337\311\2", 4) = 4
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+lseek(4, 2576, SEEK_SET) = 2576
+write(4, "\0\0\0\1", 4) = 4
+lseek(4, 2580, SEEK_SET) = 2580
+write(4, "SQLite format 3\0\4\0\1\1\0@ \0\0\5\237\0\0\0\241"..., 1024) = 1024
+lseek(4, 3604, SEEK_SET) = 3604
+write(4, "\224\337\311\2", 4) = 4
+lseek(3, 0, SEEK_SET) = 0
+write(3, "SQLite format 3\0\4\0\1\1\0@ \0\0\5\240\0\0\0\241"..., 1024) = 1024
+lseek(3, 21504, SEEK_SET) = 21504
+write(3, "\r\0\0\0\1\3\371\0\3\371\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(3, 22528, SEEK_SET) = 22528
+write(3, "\n\0\0\0\1\3\373\0\3\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+close(4) = 0
+unlink("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal") = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=2}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+getcwd("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk", 4096) = 77
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 13312, SEEK_SET) = 13312
+read(3, "\n\0\0\0\0\4\0\0\3\365\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 18432, SEEK_SET) = 18432
+read(3, "\n\0\0\0\0\4\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 1024, SEEK_SET) = 1024
+read(3, "\r\0\0\0\1\3\273\0\3\273\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+brk(0) = 0x7fb0c4e09000
+brk(0x7fb0c4e2a000) = 0x7fb0c4e2a000
+brk(0) = 0x7fb0c4e2a000
+brk(0) = 0x7fb0c4e2a000
+brk(0x7fb0c4e29000) = 0x7fb0c4e29000
+brk(0) = 0x7fb0c4e29000
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 32768, SEEK_SET) = 32768
+read(3, "\n\0\0\0\0\4\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
+openat(AT_FDCWD, "/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 4
+getdents(4, /* 8 entries */, 32768) = 328
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/.", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/..", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/aprx-manual-pics.odp", {st_mode=S_IFREG|0664, st_size=12644, ...}) = 0
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/aprx-manual.odt", {st_mode=S_IFREG|0664, st_size=197896, ...}) = 0
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/aprx-requirement-specification.odt", {st_mode=S_IFREG|0644, st_size=34162, ...}) = 0
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/aprx-manual.pdf", {st_mode=S_IFREG|0664, st_size=438348, ...}) = 0
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/aprx-requirement-specification.pdf", {st_mode=S_IFREG|0644, st_size=175248, ...}) = 0
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/.~lock.aprx-manual.odt#", {st_mode=S_IFREG|0664, st_size=95, ...}) = 0
+getdents(4, /* 0 entries */, 32768) = 0
+close(4) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 26624, SEEK_SET) = 26624
+read(3, "\2\3\360\0\2\3\277\0\0\0\0t\3\277\3\322\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(3, 117760, SEEK_SET) = 117760
+read(3, "\n\0\0\0\21\1\265\0\1\265\1\316\1\360\2\n\2-\2L\2k\2\234\2\315\2\357\3\16\0030"..., 1024) = 1024
+lseek(3, 68608, SEEK_SET) = 68608
+read(3, "\r\0\0\0\4\0\23\0\2\273\2\"\1\2\0\23\0\0\0\201lO\31\t+\10\35\t7\2\31\0"..., 1024) = 1024
+lseek(3, 105472, SEEK_SET) = 105472
+read(3, "\r\0\0\0\3\0\311\0\0\311\1\341\2\350\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(3, 64512, SEEK_SET) = 64512
+read(3, "\r\0\0\0\3\0e\0\0e\2\250\1P\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(3, 15360, SEEK_SET) = 15360
+read(3, "\n\0\0\0\0\4\0\0\3\364\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/aprx-manual.odt", {st_mode=S_IFREG|0664, st_size=197896, ...}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/aprx-manual.odt", {st_mode=S_IFREG|0664, st_size=197896, ...}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 10240, SEEK_SET) = 10240
+read(3, "\2\0\0\0\1\3\310\0\0\0\0\213\3\310\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(3, 140288, SEEK_SET) = 140288
+read(3, "\2\1\325\0\f\1-\2\0\0\0|\2B\2\n\2\352\1-\2z\1e\2\263\3\311\3X\3!"..., 1024) = 1024
+lseek(3, 88064, SEEK_SET) = 88064
+read(3, "\n\0\0\0\16\1/\0\1/\1b\1\225\1\311\1\375\0020\2c\2\227\2\312\2\376\0031\3e"..., 1024) = 1024
+lseek(3, 9216, SEEK_SET) = 9216
+read(3, "\5\0\0\0&\3(\0\0\0\0\240\3\373\3\366\3\361\3\354\3\347\3\342\3\335\3\330\3\323\3\316"..., 1024) = 1024
+lseek(3, 150528, SEEK_SET) = 150528
+read(3, "\r\0\0\0\n\0F\0\3\241\3A\2\341\2\202\2\"\1\303\1d\1\5\0\245\0F\0\0\0\0"..., 1024) = 1024
+open("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/pristine/5b/5b78dd1d69e60a7ae82aec254bcc346289c7895d.svn-base", O_RDONLY|O_CLOEXEC) = 4
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+close(4) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/aprx-manual.pdf", {st_mode=S_IFREG|0664, st_size=438348, ...}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/aprx-manual.pdf", {st_mode=S_IFREG|0664, st_size=438348, ...}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 98304, SEEK_SET) = 98304
+read(3, "\n\0\0\0\17\0\367\0\1+\1_\1\223\1\307\1\373\2.\2b\2\226\2\311\2\374\0\367\0030"..., 1024) = 1024
+lseek(3, 145408, SEEK_SET) = 145408
+read(3, "\r\0\0\0\n\0F\0\3\241\3A\2\342\2\203\2#\1\303\1d\1\5\0\246\0F\0\0\0\0"..., 1024) = 1024
+open("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/pristine/80/801e13de8f6f628e0af791392a3cdbe991e46277.svn-base", O_RDONLY|O_CLOEXEC) = 4
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+close(4) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+epoll_create1(EPOLL_CLOEXEC) = 4
+socket(PF_NETLINK, SOCK_RAW, 0) = 5
+bind(5, {sa_family=AF_NETLINK, pid=0, groups=00000000}, 12) = 0
+getsockname(5, {sa_family=AF_NETLINK, pid=5383, groups=00000000}, [12]) = 0
+sendto(5, "\24\0\0\0\26\0\1\3(#0S\0\0\0\0\0\0\0\0", 20, 0, {sa_family=AF_NETLINK, pid=0, groups=00000000}, 12) = 20
+recvmsg(5, {msg_name(12)={sa_family=AF_NETLINK, pid=0, groups=00000000}, msg_iov(1)=[{"D\0\0\0\24\0\2\0(#0S\7\25\0\0\2\10\200\376\1\0\0\0\10\0\1\0\177\0\0\1"..., 4096}], msg_controllen=0, msg_flags=0}, 0) = 148
+recvmsg(5, {msg_name(12)={sa_family=AF_NETLINK, pid=0, groups=00000000}, msg_iov(1)=[{"H\0\0\0\24\0\2\0(#0S\7\25\0\0\n\200\200\376\1\0\0\0\24\0\1\0\0\0\0\0"..., 4096}], msg_controllen=0, msg_flags=0}, 0) = 216
+recvmsg(5, {msg_name(12)={sa_family=AF_NETLINK, pid=0, groups=00000000}, msg_iov(1)=[{"\24\0\0\0\3\0\2\0(#0S\7\25\0\0\0\0\0\0", 4096}], msg_controllen=0, msg_flags=0}, 0) = 20
+socket(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 6
+connect(6, {sa_family=AF_LOCAL, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
+close(6) = 0
+close(5) = 0
+socket(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 5
+connect(5, {sa_family=AF_LOCAL, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
+close(5) = 0
+open("/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 5
+fstat(5, {st_mode=S_IFREG|0644, st_size=1751, ...}) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41f4000
+read(5, "#\n# /etc/nsswitch.conf\n#\n# An ex"..., 4096) = 1751
+read(5, "", 4096) = 0
+close(5) = 0
+munmap(0x7fb0c41f4000, 4096) = 0
+open("/etc/host.conf", O_RDONLY|O_CLOEXEC) = 5
+fstat(5, {st_mode=S_IFREG|0644, st_size=9, ...}) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41f4000
+read(5, "multi on\n", 4096) = 9
+read(5, "", 4096) = 0
+close(5) = 0
+munmap(0x7fb0c41f4000, 4096) = 0
+futex(0x7fb0c053d3d0, FUTEX_WAKE_PRIVATE, 2147483647) = 0
+open("/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 5
+fstat(5, {st_mode=S_IFREG|0644, st_size=94, ...}) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41f4000
+read(5, "# Generated by NetworkManager\nse"..., 4096) = 94
+read(5, "", 4096) = 0
+close(5) = 0
+munmap(0x7fb0c41f4000, 4096) = 0
+open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 5
+fstat(5, {st_mode=S_IFREG|0644, st_size=212134, ...}) = 0
+mmap(NULL, 212134, PROT_READ, MAP_PRIVATE, 5, 0) = 0x7fb0c41c1000
+close(5) = 0
+open("/lib64/libnss_files.so.2", O_RDONLY|O_CLOEXEC) = 5
+read(5, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0@\"\0\0\0\0\0\0"..., 832) = 832
+fstat(5, {st_mode=S_IFREG|0755, st_size=57976, ...}) = 0
+mmap(NULL, 2144360, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 5, 0) = 0x7fb0b629e000
+mprotect(0x7fb0b62a9000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0b64a8000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 5, 0xa000) = 0x7fb0b64a8000
+close(5) = 0
+mprotect(0x7fb0b64a8000, 4096, PROT_READ) = 0
+munmap(0x7fb0c41c1000, 212134) = 0
+open("/etc/hosts", O_RDONLY|O_CLOEXEC) = 5
+fstat(5, {st_mode=S_IFREG|0644, st_size=210, ...}) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41f4000
+read(5, "127.0.0.1 localhost localhost."..., 4096) = 210
+read(5, "", 4096) = 0
+close(5) = 0
+munmap(0x7fb0c41f4000, 4096) = 0
+open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 5
+fstat(5, {st_mode=S_IFREG|0644, st_size=212134, ...}) = 0
+mmap(NULL, 212134, PROT_READ, MAP_PRIVATE, 5, 0) = 0x7fb0c41c1000
+close(5) = 0
+open("/lib64/libnss_dns.so.2", O_RDONLY|O_CLOEXEC) = 5
+read(5, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\21\0\0\0\0\0\0"..., 832) = 832
+fstat(5, {st_mode=S_IFREG|0755, st_size=27512, ...}) = 0
+mmap(NULL, 2117888, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 5, 0) = 0x7fb0b6098000
+mprotect(0x7fb0b609d000, 2093056, PROT_NONE) = 0
+mmap(0x7fb0b629c000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 5, 0x4000) = 0x7fb0b629c000
+close(5) = 0
+mprotect(0x7fb0b629c000, 4096, PROT_READ) = 0
+munmap(0x7fb0c41c1000, 212134) = 0
+socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 5
+connect(5, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.10.10.53")}, 16) = 0
+poll([{fd=5, events=POLLOUT}], 1, 0) = 1 ([{fd=5, revents=POLLOUT}])
+sendmmsg(5, {{{msg_name(0)=NULL, msg_iov(1)=[{"\36\326\1\0\0\1\0\0\0\0\0\0\4repo\3ham\2fi\0\0\1\0\1", 29}], msg_controllen=0, msg_flags=0}, 29}, {{msg_name(0)=NULL, msg_iov(1)=[{"\2125\1\0\0\1\0\0\0\0\0\0\4repo\3ham\2fi\0\0\34\0\1", 29}], msg_controllen=0, msg_flags=0}, 29}}, 2, MSG_NOSIGNAL) = 2
+poll([{fd=5, events=POLLIN}], 1, 5000) = 1 ([{fd=5, revents=POLLIN}])
+ioctl(5, FIONREAD, [508]) = 0
+recvfrom(5, "\36\326\201\200\0\1\0\1\0\r\0\f\4repo\3ham\2fi\0\0\1\0\1\300\f\0"..., 2048, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.10.10.53")}, [16]) = 508
+poll([{fd=5, events=POLLIN}], 1, 4998) = 1 ([{fd=5, revents=POLLIN}])
+ioctl(5, FIONREAD, [99]) = 0
+recvfrom(5, "\2125\201\200\0\1\0\0\0\1\0\0\4repo\3ham\2fi\0\0\34\0\1\300\21\0"..., 1540, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.10.10.53")}, [16]) = 99
+close(5) = 0
+brk(0) = 0x7fb0c4e29000
+brk(0x7fb0c4e4b000) = 0x7fb0c4e4b000
+socket(PF_INET, SOCK_STREAM|SOCK_CLOEXEC, IPPROTO_TCP) = 5
+fcntl(5, F_GETFL) = 0x2 (flags O_RDWR)
+fcntl(5, F_SETFL, O_RDWR|O_NONBLOCK) = 0
+setsockopt(5, SOL_TCP, TCP_NODELAY, [1], 4) = 0
+connect(5, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("193.19.136.46")}, 16) = -1 EINPROGRESS (Operation now in progress)
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = -1 ENOENT (No such file or directory)
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLOUT, {u32=3303153608, u64=140397194125256}}) = 0
+epoll_wait(4, {{EPOLLOUT, {u32=3303153608, u64=140397194125256}}}, 16, 500) = 1
+writev(5, [{"OPTIONS /svn/aprx/trunk/doc HTTP"..., 38}, {"Host", 4}, {": ", 2}, {"repo.ham.fi", 11}, {"\r\n", 2}, {"User-Agent", 10}, {": ", 2}, {"SVN/1.8.8 (x86_64-redhat-linux-g"..., 46}, {"\r\n", 2}, {"Content-Type", 12}, {": ", 2}, {"text/xml", 8}, {"\r\n", 2}, {"Connection", 10}, {": ", 2}, {"keep-alive", 10}, {"\r\n", 2}, {"Accept-Encoding", 15}, {": ", 2}, {"gzip", 4}, {"\r\n", 2}, {"DAV", 3}, {": ", 2}, {"http://subversion.tigris.org/xml"..., 48}, {"\r\n", 2}, {"DAV", 3}, {": ", [...]
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN, {u32=3303153656, u64=140397194125304}}) = 0
+epoll_wait(4, {{EPOLLIN, {u32=3303153656, u64=140397194125304}}}, 16, 500) = 1
+read(5, "HTTP/1.1 200 OK\r\nDate: Mon, 24 M"..., 8000) = 860
+brk(0) = 0x7fb0c4e4b000
+brk(0x7fb0c4e6c000) = 0x7fb0c4e6c000
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLOUT, {u32=3303153608, u64=140397194125256}}) = 0
+epoll_wait(4, {{EPOLLOUT, {u32=3303153608, u64=140397194125256}}}, 16, 500) = 1
+writev(5, [{"OPTIONS /svn/aprx/trunk/doc HTTP"..., 38}, {"Host", 4}, {": ", 2}, {"repo.ham.fi", 11}, {"\r\n", 2}, {"User-Agent", 10}, {": ", 2}, {"SVN/1.8.8 (x86_64-redhat-linux-g"..., 46}, {"\r\n", 2}, {"Accept-Encoding", 15}, {": ", 2}, {"gzip", 4}, {"\r\n", 2}, {"DAV", 3}, {": ", 2}, {"http://subversion.tigris.org/xml"..., 48}, {"\r\n", 2}, {"DAV", 3}, {": ", 2}, {"http://subversion.tigris.org/xml"..., 52}, {"\r\n", 2}, {"DAV", 3}, {": ", 2}, {"http://subversion.tigris.org/xml"..., 55 [...]
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN, {u32=3303153656, u64=140397194125304}}) = 0
+epoll_wait(4, {{EPOLLIN, {u32=3303153656, u64=140397194125304}}}, 16, 500) = 1
+read(5, "HTTP/1.1 200 OK\r\nDate: Mon, 24 M"..., 8000) = 754
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLOUT, {u32=3303153608, u64=140397194125256}}) = 0
+epoll_wait(4, {{EPOLLOUT, {u32=3303153608, u64=140397194125256}}}, 16, 500) = 1
+writev(5, [{"PROPFIND /svn/aprx/trunk/doc HTT"..., 39}, {"Host", 4}, {": ", 2}, {"repo.ham.fi", 11}, {"\r\n", 2}, {"User-Agent", 10}, {": ", 2}, {"SVN/1.8.8 (x86_64-redhat-linux-g"..., 46}, {"\r\n", 2}, {"Content-Type", 12}, {": ", 2}, {"text/xml", 8}, {"\r\n", 2}, {"Accept-Encoding", 15}, {": ", 2}, {"gzip", 4}, {"\r\n", 2}, {"DAV", 3}, {": ", 2}, {"http://subversion.tigris.org/xml"..., 48}, {"\r\n", 2}, {"DAV", 3}, {": ", 2}, {"http://subversion.tigris.org/xml"..., 52}, {"\r\n", 2}, {" [...]
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN, {u32=3303153656, u64=140397194125304}}) = 0
+epoll_wait(4, {{EPOLLIN, {u32=3303153656, u64=140397194125304}}}, 16, 500) = 1
+read(5, "HTTP/1.1 207 Multi-Status\r\nDate:"..., 8000) = 554
+stat("/dev/random", {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 8), ...}) = 0
+open("/dev/urandom", O_RDONLY|O_CLOEXEC) = 6
+fcntl(6, F_GETFD) = 0x1 (flags FD_CLOEXEC)
+fcntl(6, F_SETFD, FD_CLOEXEC) = 0
+getuid() = 530
+getppid() = 5378
+read(6, "\264C8\327\255\304#\352\0163g\0\370\27\351\255", 16) = 16
+close(6) = 0
+gettid() = 5383
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLOUT, {u32=3303153608, u64=140397194125256}}) = 0
+epoll_wait(4, {{EPOLLOUT, {u32=3303153608, u64=140397194125256}}}, 16, 500) = 1
+writev(5, [{"MKACTIVITY /svn/aprx/!svn/act/9c"..., 77}, {"Host", 4}, {": ", 2}, {"repo.ham.fi", 11}, {"\r\n", 2}, {"User-Agent", 10}, {": ", 2}, {"SVN/1.8.8 (x86_64-redhat-linux-g"..., 46}, {"\r\n", 2}, {"Accept-Encoding", 15}, {": ", 2}, {"gzip", 4}, {"\r\n", 2}, {"DAV", 3}, {": ", 2}, {"http://subversion.tigris.org/xml"..., 48}, {"\r\n", 2}, {"DAV", 3}, {": ", 2}, {"http://subversion.tigris.org/xml"..., 52}, {"\r\n", 2}, {"DAV", 3}, {": ", 2}, {"http://subversion.tigris.org/xml"..., 55 [...]
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN, {u32=3303153656, u64=140397194125304}}) = 0
+epoll_wait(4, {{EPOLLIN, {u32=3303153656, u64=140397194125304}}}, 16, 500) = 1
+read(5, "HTTP/1.1 401 Authorization Requi"..., 8000) = 600
+lstat("/localhome/mea/.subversion/auth/svn.simple/5ab2e05495eafc7b602c32da117c5d46", 0x7fff601051f0) = -1 ENOENT (No such file or directory)
+open("/dev/tty", O_RDWR|O_CLOEXEC) = 6
+ioctl(6, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
+ioctl(6, SNDCTL_TMR_CONTINUE or SNDRV_TIMER_IOCTL_GPARAMS or TCSETSF, {B38400 opost -isig -icanon -echo ...}) = 0
+write(6, "Authentication realm: <http://re"..., 68) = 68
+ioctl(6, SNDCTL_TMR_START or SNDRV_TIMER_IOCTL_TREAD or TCSETS, {B38400 opost isig icanon echo ...}) = 0
+ioctl(6, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
+close(6) = 0
+open("/dev/tty", O_RDWR|O_CLOEXEC) = 6
+ioctl(6, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
+ioctl(6, SNDCTL_TMR_CONTINUE or SNDRV_TIMER_IOCTL_GPARAMS or TCSETSF, {B38400 opost -isig -icanon -echo ...}) = 0
+write(6, "Password for 'oh2mqk': ", 23) = 23
+read(6, "r", 1) = 1
+write(6, "*", 1) = 1
+read(6, "i", 1) = 1
+write(6, "*", 1) = 1
+read(6, "f", 1) = 1
+write(6, "*", 1) = 1
+read(6, "r", 1) = 1
+write(6, "*", 1) = 1
+read(6, "a", 1) = 1
+write(6, "*", 1) = 1
+read(6, "f", 1) = 1
+write(6, "*", 1) = 1
+read(6, "2", 1) = 1
+write(6, "*", 1) = 1
+read(6, "2", 1) = 1
+write(6, "*", 1) = 1
+read(6, "\n", 1) = 1
+write(6, "\n", 1) = 1
+write(6, "\n", 1) = 1
+ioctl(6, SNDCTL_TMR_START or SNDRV_TIMER_IOCTL_TREAD or TCSETS, {B38400 opost isig icanon echo ...}) = 0
+ioctl(6, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
+close(6) = 0
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLOUT, {u32=3303153608, u64=140397194125256}}) = 0
+epoll_wait(4, {{EPOLLIN|EPOLLOUT, {u32=3303153608, u64=140397194125256}}}, 16, 500) = 1
+read(5, "", 8000) = 0
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+close(5) = 0
+socket(PF_INET, SOCK_STREAM|SOCK_CLOEXEC, IPPROTO_TCP) = 5
+fcntl(5, F_GETFL) = 0x2 (flags O_RDWR)
+fcntl(5, F_SETFL, O_RDWR|O_NONBLOCK) = 0
+setsockopt(5, SOL_TCP, TCP_NODELAY, [1], 4) = 0
+connect(5, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("193.19.136.46")}, 16) = -1 EINPROGRESS (Operation now in progress)
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = -1 ENOENT (No such file or directory)
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLOUT, {u32=3303153656, u64=140397194125304}}) = 0
+epoll_wait(4, {{EPOLLOUT, {u32=3303153656, u64=140397194125304}}}, 16, 500) = 1
+writev(5, [{"MKACTIVITY /svn/aprx/!svn/act/9c"..., 77}, {"Host", 4}, {": ", 2}, {"repo.ham.fi", 11}, {"\r\n", 2}, {"Authorization", 13}, {": ", 2}, {"Basic b2gybXFrOnJpZnJhZjIy", 26}, {"\r\n", 2}, {"User-Agent", 10}, {": ", 2}, {"SVN/1.8.8 (x86_64-redhat-linux-g"..., 46}, {"\r\n", 2}, {"Accept-Encoding", 15}, {": ", 2}, {"gzip", 4}, {"\r\n", 2}, {"DAV", 3}, {": ", 2}, {"http://subversion.tigris.org/xml"..., 48}, {"\r\n", 2}, {"DAV", 3}, {": ", 2}, {"http://subversion.tigris.org/xml"..., [...]
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN, {u32=3303153608, u64=140397194125256}}) = 0
+epoll_wait(4, {{EPOLLIN, {u32=3303153608, u64=140397194125256}}}, 16, 500) = 1
+read(5, "HTTP/1.1 201 Created\r\nDate: Mon,"..., 8000) = 550
+open("/dev/tty", O_RDWR|O_CLOEXEC) = 6
+ioctl(6, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
+ioctl(6, SNDCTL_TMR_CONTINUE or SNDRV_TIMER_IOCTL_GPARAMS or TCSETSF, {B38400 opost -isig -icanon -echo ...}) = 0
+write(6, "\n-------------------------------"..., 607) = 607
+ioctl(6, SNDCTL_TMR_START or SNDRV_TIMER_IOCTL_TREAD or TCSETS, {B38400 opost isig icanon echo ...}) = 0
+ioctl(6, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
+close(6) = 0
+open("/dev/tty", O_RDWR|O_CLOEXEC) = 6
+ioctl(6, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
+ioctl(6, SNDCTL_TMR_CONTINUE or SNDRV_TIMER_IOCTL_GPARAMS or TCSETSF, {B38400 opost -isig -icanon -echo ...}) = 0
+write(6, "Store password unencrypted (yes/"..., 37) = 37
+read(6, "r", 1) = 1
+write(6, "r", 1) = 1
+read(6, "a", 1) = 1
+write(6, "a", 1) = 1
+read(6, "\27", 1) = 1
+write(6, "\7", 1) = 1
+read(6, "\25", 1) = 1
+write(6, "\7", 1) = 1
+read(6, "\177", 1) = 1
+write(6, "\10", 1) = 1
+write(6, " ", 1) = 1
+write(6, "\10", 1) = 1
+read(6, "\177", 1) = 1
+write(6, "\10", 1) = 1
+write(6, " ", 1) = 1
+write(6, "\10", 1) = 1
+read(6, "\177", 1) = 1
+read(6, "\177", 1) = 1
+read(6, "e", 1) = 1
+write(6, "e", 1) = 1
+read(6, "\177", 1) = 1
+write(6, "\10", 1) = 1
+write(6, " ", 1) = 1
+write(6, "\10", 1) = 1
+read(6, "\177", 1) = 1
+read(6, "y", 1) = 1
+write(6, "y", 1) = 1
+read(6, "e", 1) = 1
+write(6, "e", 1) = 1
+read(6, "s", 1) = 1
+write(6, "s", 1) = 1
+read(6, "\177", 1) = 1
+write(6, "\10", 1) = 1
+write(6, " ", 1) = 1
+write(6, "\10", 1) = 1
+read(6, "\177", 1) = 1
+write(6, "\10", 1) = 1
+write(6, " ", 1) = 1
+write(6, "\10", 1) = 1
+read(6, "\177", 1) = 1
+write(6, "\10", 1) = 1
+write(6, " ", 1) = 1
+write(6, "\10", 1) = 1
+read(6, "\177", 1) = 1
+read(6, "\177", 1) = 1
+read(6, "n", 1) = 1
+write(6, "n", 1) = 1
+read(6, "o", 1) = 1
+write(6, "o", 1) = 1
+read(6, "\177", 1) = 1
+write(6, "\10", 1) = 1
+write(6, " ", 1) = 1
+write(6, "\10", 1) = 1
+read(6, "\177", 1) = 1
+write(6, "\10", 1) = 1
+write(6, " ", 1) = 1
+write(6, "\10", 1) = 1
+read(6, "\177", 1) = 1
+read(6, "\177", 1) = 1
+read(6, "y", 1) = 1
+write(6, "y", 1) = 1
+read(6, "e", 1) = 1
+write(6, "e", 1) = 1
+read(6, "s", 1) = 1
+write(6, "s", 1) = 1
+read(6, "\n", 1) = 1
+write(6, "\n", 1) = 1
+ioctl(6, SNDCTL_TMR_START or SNDRV_TIMER_IOCTL_TREAD or TCSETS, {B38400 opost isig icanon echo ...}) = 0
+ioctl(6, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
+close(6) = 0
+open("/localhome/mea/.subversion/auth/svn.simple/5ab2e05495eafc7b602c32da117c5d46", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 6
+write(6, "K 8\npasstype\nV 6\nsimple\nK 8\npass"..., 150) = 150
+close(6) = 0
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLOUT, {u32=3303153656, u64=140397194125304}}) = 0
+epoll_wait(4, {{EPOLLIN|EPOLLOUT, {u32=3303153656, u64=140397194125304}}}, 16, 500) = 1
+read(5, "", 8000) = 0
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+close(5) = 0
+socket(PF_INET, SOCK_STREAM|SOCK_CLOEXEC, IPPROTO_TCP) = 5
+fcntl(5, F_GETFL) = 0x2 (flags O_RDWR)
+fcntl(5, F_SETFL, O_RDWR|O_NONBLOCK) = 0
+setsockopt(5, SOL_TCP, TCP_NODELAY, [1], 4) = 0
+connect(5, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("193.19.136.46")}, 16) = -1 EINPROGRESS (Operation now in progress)
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = -1 ENOENT (No such file or directory)
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLOUT, {u32=3303153608, u64=140397194125256}}) = 0
+epoll_wait(4, {{EPOLLOUT, {u32=3303153608, u64=140397194125256}}}, 16, 500) = 1
+writev(5, [{"PROPFIND /svn/aprx/trunk/doc HTT"..., 39}, {"Host", 4}, {": ", 2}, {"repo.ham.fi", 11}, {"\r\n", 2}, {"Authorization", 13}, {": ", 2}, {"Basic b2gybXFrOnJpZnJhZjIy", 26}, {"\r\n", 2}, {"User-Agent", 10}, {": ", 2}, {"SVN/1.8.8 (x86_64-redhat-linux-g"..., 46}, {"\r\n", 2}, {"Content-Type", 12}, {": ", 2}, {"text/xml", 8}, {"\r\n", 2}, {"Accept-Encoding", 15}, {": ", 2}, {"gzip", 4}, {"\r\n", 2}, {"DAV", 3}, {": ", 2}, {"http://subversion.tigris.org/xml"..., 48}, {"\r\n", 2}, [...]
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN, {u32=3303153656, u64=140397194125304}}) = 0
+epoll_wait(4, {{EPOLLIN, {u32=3303153656, u64=140397194125304}}}, 16, 500) = 1
+read(5, "HTTP/1.1 207 Multi-Status\r\nDate:"..., 8000) = 443
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLOUT, {u32=3303153608, u64=140397194125256}}) = 0
+epoll_wait(4, {{EPOLLOUT, {u32=3303153608, u64=140397194125256}}}, 16, 500) = 1
+writev(5, [{"CHECKOUT /svn/aprx/!svn/vcc/defa"..., 46}, {"Host", 4}, {": ", 2}, {"repo.ham.fi", 11}, {"\r\n", 2}, {"Authorization", 13}, {": ", 2}, {"Basic b2gybXFrOnJpZnJhZjIy", 26}, {"\r\n", 2}, {"User-Agent", 10}, {": ", 2}, {"SVN/1.8.8 (x86_64-redhat-linux-g"..., 46}, {"\r\n", 2}, {"Content-Type", 12}, {": ", 2}, {"text/xml", 8}, {"\r\n", 2}, {"Accept-Encoding", 15}, {": ", 2}, {"gzip", 4}, {"\r\n", 2}, {"DAV", 3}, {": ", 2}, {"http://subversion.tigris.org/xml"..., 48}, {"\r\n", 2}, [...]
+epoll_wait(4, {{EPOLLOUT, {u32=3303153608, u64=140397194125256}}}, 16, 500) = 1
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN, {u32=3303153656, u64=140397194125304}}) = 0
+epoll_wait(4, {{EPOLLIN, {u32=3303153656, u64=140397194125304}}}, 16, 500) = 1
+read(5, "HTTP/1.1 201 Created\r\nDate: Mon,"..., 8000) = 566
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLOUT, {u32=3303153608, u64=140397194125256}}) = 0
+epoll_wait(4, {{EPOLLOUT, {u32=3303153608, u64=140397194125256}}}, 16, 500) = 1
+writev(5, [{"PROPPATCH /svn/aprx/!svn/wbl/9ce"..., 80}, {"Host", 4}, {": ", 2}, {"repo.ham.fi", 11}, {"\r\n", 2}, {"Authorization", 13}, {": ", 2}, {"Basic b2gybXFrOnJpZnJhZjIy", 26}, {"\r\n", 2}, {"User-Agent", 10}, {": ", 2}, {"SVN/1.8.8 (x86_64-redhat-linux-g"..., 46}, {"\r\n", 2}, {"Accept-Encoding", 15}, {": ", 2}, {"gzip", 4}, {"\r\n", 2}, {"DAV", 3}, {": ", 2}, {"http://subversion.tigris.org/xml"..., 48}, {"\r\n", 2}, {"DAV", 3}, {": ", 2}, {"http://subversion.tigris.org/xml"..., [...]
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN, {u32=3303153656, u64=140397194125304}}) = 0
+epoll_wait(4, {{EPOLLIN, {u32=3303153656, u64=140397194125304}}}, 16, 500) = 1
+read(5, "HTTP/1.1 207 Multi-Status\r\nDate:"..., 8000) = 464
+fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
+mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41f4000
+write(1, "Sending doc/aprx-manual.o"..., 35) = 35
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/aprx-manual.odt", {st_mode=S_IFREG|0664, st_size=197896, ...}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLOUT, {u32=3303153608, u64=140397194125256}}) = 0
+epoll_wait(4, {{EPOLLOUT, {u32=3303153608, u64=140397194125256}}}, 16, 500) = 1
+writev(5, [{"CHECKOUT /svn/aprx/!svn/ver/574/"..., 68}, {"Host", 4}, {": ", 2}, {"repo.ham.fi", 11}, {"\r\n", 2}, {"Authorization", 13}, {": ", 2}, {"Basic b2gybXFrOnJpZnJhZjIy", 26}, {"\r\n", 2}, {"User-Agent", 10}, {": ", 2}, {"SVN/1.8.8 (x86_64-redhat-linux-g"..., 46}, {"\r\n", 2}, {"Content-Type", 12}, {": ", 2}, {"text/xml", 8}, {"\r\n", 2}, {"Accept-Encoding", 15}, {": ", 2}, {"gzip", 4}, {"\r\n", 2}, {"DAV", 3}, {": ", 2}, {"http://subversion.tigris.org/xml"..., 48}, {"\r\n", 2}, [...]
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN, {u32=3303153656, u64=140397194125304}}) = 0
+epoll_wait(4, {{EPOLLIN, {u32=3303153656, u64=140397194125304}}}, 16, 500) = 1
+read(5, "HTTP/1.1 201 Created\r\nDate: Mon,"..., 8000) = 602
+write(1, "Sending doc/aprx-manual.p"..., 35) = 35
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/aprx-manual.pdf", {st_mode=S_IFREG|0664, st_size=438348, ...}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLOUT, {u32=3303153608, u64=140397194125256}}) = 0
+epoll_wait(4, {{EPOLLOUT, {u32=3303153608, u64=140397194125256}}}, 16, 500) = 1
+writev(5, [{"CHECKOUT /svn/aprx/!svn/ver/569/"..., 68}, {"Host", 4}, {": ", 2}, {"repo.ham.fi", 11}, {"\r\n", 2}, {"Authorization", 13}, {": ", 2}, {"Basic b2gybXFrOnJpZnJhZjIy", 26}, {"\r\n", 2}, {"User-Agent", 10}, {": ", 2}, {"SVN/1.8.8 (x86_64-redhat-linux-g"..., 46}, {"\r\n", 2}, {"Content-Type", 12}, {": ", 2}, {"text/xml", 8}, {"\r\n", 2}, {"Accept-Encoding", 15}, {": ", 2}, {"gzip", 4}, {"\r\n", 2}, {"DAV", 3}, {": ", 2}, {"http://subversion.tigris.org/xml"..., 48}, {"\r\n", 2}, [...]
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN, {u32=3303153656, u64=140397194125304}}) = 0
+epoll_wait(4, {{EPOLLIN, {u32=3303153656, u64=140397194125304}}}, 16, 500) = 1
+read(5, "HTTP/1.1 201 Created\r\nDate: Mon,"..., 8000) = 602
+write(1, "Transmitting file data .", 24) = 24
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/aprx-manual.odt", {st_mode=S_IFREG|0664, st_size=197896, ...}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+open("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/aprx-manual.odt", O_RDONLY|O_CLOEXEC) = 6
+open("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/tmp/svn-VYI0r2", O_RDWR|O_CREAT|O_EXCL, 0600) = 7
+fcntl(7, F_GETFD) = 0
+fcntl(7, F_SETFD, FD_CLOEXEC) = 0
+open("/tmp/apr-tmp.fOowVz", O_RDWR|O_CREAT|O_EXCL, 0600) = 8
+fcntl(8, F_GETFD) = 0
+fcntl(8, F_SETFD, FD_CLOEXEC) = 0
+write(8, "!", 1) = 1
+close(8) = 0
+unlink("/tmp/apr-tmp.fOowVz") = 0
+open("/tmp/svn-0c067acf.tmp", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 8
+fstat(8, {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
+close(8) = 0
+unlink("/tmp/svn-0c067acf.tmp") = 0
+fstat(7, {st_mode=S_IFREG|0600, st_size=0, ...}) = 0
+chmod("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/tmp/svn-VYI0r2", 0664) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+open("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/pristine/5b/5b78dd1d69e60a7ae82aec254bcc346289c7895d.svn-base", O_RDONLY|O_CLOEXEC) = 8
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+open("/tmp/svn-4fJ8w7", O_RDWR|O_CREAT|O_EXCL, 0600) = 9
+fcntl(9, F_GETFD) = 0
+fcntl(9, F_SETFD, FD_CLOEXEC) = 0
+mmap(NULL, 212992, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c4174000
+read(8, "PK\3\4\24\0\0\10\0\0\271dhD^\3062\f'\0\0\0'\0\0\0\10\0\0\0mi"..., 4096) = 4096
+read(8, "\26\256\3365\36\v\34\303:\343=\342\234x j\204\33Vt\21yb\202X\n\353\242#\377\177\34"..., 4096) = 4096
+read(8, "5\270\233\351M\206\346\245\324\370\374\271\277s-v_U\236:\331\3044\345\312-'\263W\35O\235"..., 4096) = 4096
+read(8, "\272\263\245\347\367\302\2075\374\202\374N?\247\25\300\1&\302\200\250\231_iM\337\343 E\317\264\320"..., 4096) = 4096
+read(8, ";\222\352\271\212.\22\3657\17h\233\27\317\267y\227\v\243n\260\34\315\260\370\353\306!\270\240B\316"..., 4096) = 4096
+read(8, "\357\253}U\35\250\3728+\354nxh]dk\364j|s\242\222\2609\336\236\343\315*k\355\220"..., 4096) = 4096
+read(8, "} \362]Ws\33\25{\372\23z\t\201\4\237~\370\362\356g\205'\325\346\"\30/\277&Rl"..., 4096) = 4096
+read(8, "^]\251]GY\313=a\240\277\31\277\350\232\256\16\6\23]-\350\10\23\267\300\2o\266\364=\27"..., 4096) = 4096
+read(8, "\203\224\4\362E9\225\264g\257\245K\253\204\21~\320\r\204\356\233X\235\341b\\\0\5^R3\6"..., 4096) = 4096
+read(8, "\330\3734\273\326`\213m7M\215\331\334R\313\203\272\237\344\224\273\233\336\356\203j\236D\320\276fr"..., 4096) = 4096
+read(8, "\212\302Y\345\3216Lcd\230#k`\33p)\230\243\241Y\26h\374\336\264\32\35m4\271\362\335"..., 4096) = 4096
+read(8, "G\200\342K\240\245\316%\326{U~f4<%%\333\336<\225\351\360\224\325\251\353*\335\221\266\32"..., 4096) = 4096
+read(8, "\27\362\254U\345\232\212\340L\241\31'\361&\232+\366S\265\204\345,D^\222\325\261\341\2439\221~"..., 4096) = 4096
+read(8, "i\301\363\16\213\304\23\nm\251'Y\372\322\0u\364\247<\301U\267\325A\35(\260\22i3\16\225"..., 4096) = 4096
+read(8, "A\343F\35\324 at 7\352P\220Ckj\177\261\350\356\222?\334\342\244a\277\4}\215\213\211\24\232q"..., 4096) = 4096
+read(8, "*l\243\263\7\222v\314\33\3\364\264\251)\0074\313-\243\316<\31\357\223-y\16\300\2353}\225"..., 4096) = 4096
+read(8, "\314\365\1\374\222\316L\326\315\0\373\374\3\355\200\317\255[[\3047\7\230<\6\16p\213\2q\377\313"..., 4096) = 4096
+read(8, "\253\5\f\23hE\255\361\246v)O\316\336\207l\36\367\257\3237\\\235\206.\10\212\"\227\363\343\311"..., 4096) = 4096
+read(8, "\326\237\320\354\332\377\325\24\374\316\25\2473\205\234\206\267\341\252\261\332\241\354\321\341\365\370\233\352\315\315\344"..., 4096) = 4096
+read(8, "\214\316\307\346\364d\223N\225\343\227\365\31$m0\307\310(T\355S\342\27\246\2$\211.k\245n"..., 4096) = 4096
+read(8, "\301\30w+\246\257\227ivVF\233\374v'\5\201d\345\237bZ\323N.C\226=\252f&p"..., 4096) = 4096
+read(8, "V\246A\366\6\206\v\323\325\362\267\254(\377u\\\334\210$\216\330\212;\240\231\21/A\313\375#~"..., 4096) = 4096
+read(8, "&\177\325\232\357X\204\223H\237(Jy\347\274\27T\275\355\27i\257b\35\33\27\217\277g\266\347\274"..., 4096) = 4096
+read(8, "Eg\264[\\<?<\266R\311\244\305J\246\215\"\374y\372\0\216\233\270Dt\355\301\377\346\211!"..., 4096) = 4096
+read(8, "\6C#\260!\343\34\344l\257\304\263E\16\326\27\205?\242d\240\275\261B\17Ik\330\0!\f@"..., 4096) = 4096
+read(6, "PK\3\4\24\0\0\10\0\0\360axD^\3062\f'\0\0\0'\0\0\0\10\0\0\0mi"..., 4096) = 4096
+read(6, "\350\305\345\33_GTU\377|\203\225\344\344\344\204B\373A\21\233\364\330\244\322J\360\206t\302\237v"..., 4096) = 4096
+read(6, "\34\316f\263\234U\304\337Q\230\3019\317d\261K+\336\311\347<\223\v\315 at y'\237\363L\26\32"..., 4096) = 4096
+read(6, "\27\17\321\36\314\242\213\207h\23\222\321\305C\264\7\337\350\342!Z\205z\224\214\207\230u\361\20\r\303"..., 4096) = 4096
+read(6, "\326\252\234\370@\373#{\274\367\3\233\364\365\264;\35U\327C^\346\20\357\207\37\t=\310\216\256\34"..., 4096) = 4096
+read(6, "Qx\320\26<\260E\264\200\2\364\302\335# \346h>\24\200\223\260\26\336#!\266\4\360\347\24\330"..., 4096) = 4096
+read(6, "Y\16f\211\1\362\242\364\v(\361\274\370\261G\"\27}\3006\337\234(l\235\3057*\177+T~"..., 4096) = 4096
+read(6, "\201\205\37\340\265\245Dm \3366b\323\0025\346,?%\376\262\372]PJXSN\356t\34{"..., 4096) = 4096
+read(6, "\35\204T\20\4\377\366\351z\306\334\217n3<\334\34\351\241\207\252C\221\f\213\3618AG\"\256\4"..., 4096) = 4096
+read(6, "\0103\\\275s\304\274C\5s0\366\7\327\224\20\3k\310\224\220\365%r\356F,3I\237'\211"..., 4096) = 4096
+read(6, "p\256\24\230\203\354\275\306\1b0\201\202\251\23\35f\333\216\341\7\2V2\305\352\310!E\347\211\327"..., 4096) = 4096
+read(6, "\204\311\212\307^\"2\2752\310\255AO\337\304\370\262\232H\336et\343\23q\300\375\220\0027\30\212"..., 4096) = 4096
+read(6, "N\260\372\330<ez\31#\366\225\337\30D\323Y\200\364r\211X\202\201\317Yw\357\375\313\0\r\265"..., 4096) = 4096
+read(6, "w\27p\16=<\305\21*_i5A\357\340\361\f\205\16\230\352'U\302/\223W\200t\301\373\224"..., 4096) = 4096
+read(6, "x\224)\247d\260\261\351aW'J\234F&\26\345.\236\237\237\377\376m\332\365?\317\371\36t\223"..., 4096) = 4096
+read(6, "\3639\314~\213\353\0\243\321\22\305g\246\373\34\330\25\377\374iz\332k\332T\351t\354<\302\314\252"..., 4096) = 4096
+read(6, "kjs?\374P\234\25T\270\6\303*\351ur\2\335\3161\37Q\352\263\37\370\246\215J\253\304\366"..., 4096) = 4096
+read(6, "\377yDq\265{\23\233i.k\226^\216\f\243\211)QQk\341r\364\204\247\2333\275\334\207v"..., 4096) = 4096
+read(6, ".\270\327\327\320\210\302eb\3543\236r\346g\302Zm\333\227\272\324pS\213\336\203\206*5U\346"..., 4096) = 4096
+read(6, "V9l\344\370\247\3217\220)\1\200\t\347\3412\275\255\274\345\242e\235O\334\242\251gH\302eF"..., 4096) = 4096
+read(6, "\226\16\267\33\275m\347\356\334\267o1A\3.\351erJ\252\357\266\35\356\256\263B.]puv"..., 4096) = 4096
+read(6, "/\300\0160\5\354\0S\300\16\365\2\366c\1\0\0\0\350%\0\0\0\0\275\4\0\0\0\240\227\0"..., 4096) = 4096
+read(6, "\341CmF\30\231[\f\263\265\213\210\214\244n\363q\2342\315\310\334\302q\3124\352\306\37\372\n\266"..., 4096) = 4096
+read(6, "\311\272\215>\3222RQQ\271\34x~\213\317\206\305\313V\304F\204\23B\250\377\377\265\320\236]b"..., 4096) = 4096
+read(6, "\214\355\321\313\214\376\312\312nd\350\275{eee\1\27\2\355\306\214Ul;\260\233b\357\201\203\273"..., 4096) = 4096
+write(7, "PK\3\4\24\0\0\10\0\0\360axD^\3062\f'\0\0\0'\0\0\0\10\0\0\0mi"..., 4096) = 4096
+write(7, "\350\305\345\33_GTU\377|\203\225\344\344\344\204B\373A\21\233\364\330\244\322J\360\206t\302\237v"..., 4096) = 4096
+write(7, "\34\316f\263\234U\304\337Q\230\3019\317d\261K+\336\311\347<\223\v\315 at y'\237\363L\26\32"..., 4096) = 4096
+write(7, "\27\17\321\36\314\242\213\207h\23\222\321\305C\264\7\337\350\342!Z\205z\224\214\207\230u\361\20\r\303"..., 4096) = 4096
+write(7, "\326\252\234\370@\373#{\274\367\3\233\364\365\264;\35U\327C^\346\20\357\207\37\t=\310\216\256\34"..., 4096) = 4096
+write(7, "Qx\320\26<\260E\264\200\2\364\302\335# \346h>\24\200\223\260\26\336#!\266\4\360\347\24\330"..., 4096) = 4096
+write(7, "Y\16f\211\1\362\242\364\v(\361\274\370\261G\"\27}\3006\337\234(l\235\3057*\177+T~"..., 4096) = 4096
+write(7, "\201\205\37\340\265\245Dm \3366b\323\0025\346,?%\376\262\372]PJXSN\356t\34{"..., 4096) = 4096
+write(7, "\35\204T\20\4\377\366\351z\306\334\217n3<\334\34\351\241\207\252C\221\f\213\3618AG\"\256\4"..., 4096) = 4096
+write(7, "\0103\\\275s\304\274C\5s0\366\7\327\224\20\3k\310\224\220\365%r\356F,3I\237'\211"..., 4096) = 4096
+write(7, "p\256\24\230\203\354\275\306\1b0\201\202\251\23\35f\333\216\341\7\2V2\305\352\310!E\347\211\327"..., 4096) = 4096
+write(7, "\204\311\212\307^\"2\2752\310\255AO\337\304\370\262\232H\336et\343\23q\300\375\220\0027\30\212"..., 4096) = 4096
+write(7, "N\260\372\330<ez\31#\366\225\337\30D\323Y\200\364r\211X\202\201\317Yw\357\375\313\0\r\265"..., 4096) = 4096
+write(7, "w\27p\16=<\305\21*_i5A\357\340\361\f\205\16\230\352'U\302/\223W\200t\301\373\224"..., 4096) = 4096
+write(7, "x\224)\247d\260\261\351aW'J\234F&\26\345.\236\237\237\377\376m\332\365?\317\371\36t\223"..., 4096) = 4096
+write(7, "\3639\314~\213\353\0\243\321\22\305g\246\373\34\330\25\377\374iz\332k\332T\351t\354<\302\314\252"..., 4096) = 4096
+write(7, "kjs?\374P\234\25T\270\6\303*\351ur\2\335\3161\37Q\352\263\37\370\246\215J\253\304\366"..., 4096) = 4096
+write(7, "\377yDq\265{\23\233i.k\226^\216\f\243\211)QQk\341r\364\204\247\2333\275\334\207v"..., 4096) = 4096
+write(7, ".\270\327\327\320\210\302eb\3543\236r\346g\302Zm\333\227\272\324pS\213\336\203\206*5U\346"..., 4096) = 4096
+write(7, "V9l\344\370\247\3217\220)\1\200\t\347\3412\275\255\274\345\242e\235O\334\242\251gH\302eF"..., 4096) = 4096
+write(7, "\226\16\267\33\275m\347\356\334\267o1A\3.\351erJ\252\357\266\35\356\256\263B.]puv"..., 4096) = 4096
+write(7, "/\300\0160\5\354\0S\300\16\365\2\366c\1\0\0\0\350%\0\0\0\0\275\4\0\0\0\240\227\0"..., 4096) = 4096
+write(7, "\341CmF\30\231[\f\263\265\213\210\214\244n\363q\2342\315\310\334\302q\3124\352\306\37\372\n\266"..., 4096) = 4096
+write(7, "\311\272\215>\3222RQQ\271\34x~\213\317\206\305\313V\304F\204\23B\250\377\377\265\320\236]b"..., 4096) = 4096
+mmap(NULL, 139264, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c41d2000
+write(9, "SVN\0\0\206\240\0\206\240\0\6\206\237v\n\0\200\206\237v\360axD^\3062\f'\0\0"..., 4096) = 4096
+write(9, "a\200\267{e\231\231\231\330c\205\350\305\345\33_GTU\377|\203\225\344\344\344\204B\373A\21\233"..., 4096) = 4096
+write(9, "\216f'\336Q\230\3019\317d\323\34\316f\263\234U\304\337Q\230\3019\317d\261K+\336\311\347<"..., 4096) = 4096
+write(9, "<D{p\210.\36\242M\350D\27\17\321\36\314\242\213\207h\23\222\321\305C\264\7\337\350\342!Z"..., 4096) = 4096
+write(9, "32@\232\33\23}`\216M0\326\252\234\370@\373#{\274\367\3\233\364\365\264;\35U\327C^"..., 4096) = 4096
+write(9, "\370\22\210\4v\204\30\214pY\335Qx\320\26<\260E\264\200\2\364\302\335# \346h>\24\200\223"..., 4096) = 4096
+write(9, "+\v\343\220\365Dh\341\212\257lY\16f\211\1\362\242\364\v(\361\274\370\261G\"\27}\3006\337"..., 4096) = 4096
+write(9, "\0234\34C\326\310\256\245\353\254\251\201\205\37\340\265\245Dm \3366b\323\0025\346,?%\376\262"..., 4096) = 4096
+write(9, "L\365\312\221/\214\310\273$Pl\35\204T\20\4\377\366\351z\306\334\217n3<\334\34\351\241\207\252"..., 4096) = 4096
+write(9, "\276y\v\270\267\204%\253c7\24\0103\\\275s\304\274C\5s0\366\7\327\224\20\3k\310\224\220"..., 4096) = 4096
+write(9, "l\35y\24\224\207\301\223o\323\270p\256\24\230\203\354\275\306\1b0\201\202\251\23\35f\333\216\341\7"..., 4096) = 4096
+write(9, "\315\22\27\24\362!\235\307\341D\324\204\311\212\307^\"2\2752\310\255AO\337\304\370\262\232H\336e"..., 4096) = 4096
+write(9, "\244\2\217\337|P8\212\2517\271N\260\372\330<ez\31#\366\225\337\30D\323Y\200\364r\211X"..., 4096) = 4096
+write(9, "\362\374\20\371.9?\7\23\337\vw\27p\16=<\305\21*_i5A\357\340\361\f\205\16\230\352"..., 4096) = 4096
+write(9, "-\321\245\314k\27\317f\177\373:x\224)\247d\260\261\351aW'J\234F&\26\345.\236\237\237"..., 4096) = 4096
+write(9, "?3\333IOwO7\27\346>\3639\314~\213\353\0\243\321\22\305g\246\373\34\330\25\377\374iz"..., 4096) = 4096
+write(9, "PkQ\364m8\347\0g%\250kjs?\374P\234\25T\270\6\303*\351ur\2\335\3161\37"..., 4096) = 4096
+write(9, "\355\362\350\356m\17WG\232$\212\377yDq\265{\23\233i.k\226^\216\f\243\211)QQk"..., 4096) = 4096
+write(9, "\301\262\364\0364\214\302er|\5.\270\327\327\320\210\302eb\3543\236r\346g\302Zm\333\227\272"..., 4096) = 4096
+write(9, "\221\325\226\346\226\310\243~\251g\342V9l\344\370\247\3217\220)\1\200\t\347\3412\275\255\274\345\242"..., 4096) = 4096
+write(9, "\24!\32E,n\342\323\244\5\213\226\16\267\33\275m\347\356\334\267o1A\3.\351erJ\252\357"..., 4096) = 4096
+write(9, "\205\220a?6!9[\336lW/\300\0160\5\354\0S\300\16\365\2\366c\1\0\0\0\350%\0"..., 4096) = 4096
+write(9, "V\346Y\242*p\356~\237Ga\341CmF\30\231[\f\263\265\213\210\214\244n\363q\2342\315\310"..., 4096) = 4096
+write(9, "\333\350\323\333\334,\354^\250\251\211\311\272\215>\3222RQQ\271\34x~\213\317\206\305\313V\304F"..., 4096) = 4096
+write(9, "K\206p\v\332\16\217\302\302\355\306\214\355\321\313\214\376\312\312nd\350\275{eee\1\27\2\355\306"..., 4096) = 4096
+read(8, "\237\225\0323\353\223\331j\271H~\220\343\355\232\334>\335h&\270\240 v\21\26s)\274\250\270X"..., 4096) = 4096
+read(8, "\5\2I\224\264\21\374\355B2\312\243\363\vY1Fqh\30-nL\257\214\250\322\236\5\277\315\252"..., 4096) = 4096
+read(8, "G\255\333\6\376\322\333i_\330d\252\22\23\36|\214\326\267\31\321\267\262l\323\214\277\220\272m\211\24"..., 4096) = 4096
+read(8, "\222\2403\212\347-\315\201\371tr\277\315\3275pn\356W\24C\351+\210\327h\365\16\21G\321V"..., 4096) = 4096
+read(8, "_K\212?\373;\277T\244\325\370\312w\265A)gQ:h\31\265_:\"|T\251\276\343\n!"..., 4096) = 4096
+read(8, "\256I\362d\264\352\t\266_\247i0n\222\1&\2253\261p\374,g+2\0\325\7{\322*r"..., 4096) = 4096
+read(8, "\252\361\324\2248\336\240Gf%\246\360&\312\266\t\230\377\273\215\216n\2\206\254\251\201\301\322\3104\225"..., 4096) = 4096
+read(8, "M\264\26\266\216 \344\21\16~\216\205Q\317\361\313;\316\36\2428\2711\27c\235K\240\213b\337s"..., 4096) = 4096
+read(8, "\340\v\277\334R<\312:\231h\347Ru\234\247E\214sl\343\207\307j\253\274\323/\233\255{X\213"..., 4096) = 4096
+read(8, "\310\235?\254\306\365\254.~\275\337\224V\2777\300\312\n\250{\330\203#\277\243\35\341\227H\244\215H"..., 4096) = 4096
+read(8, "\263\353\311\306\351%\341\275\317\312\337X\255hZP\246\22P\200\343\354\3\241e\266\24\322\376\251\225?"..., 4096) = 4096
+read(8, "4\331\313\37\263\265J\233\303!2\242\336D\\&\337\331f\373\213`\264\324\215\304\v6'\353^^"..., 4096) = 4096
+read(8, "t\216\276\245P\224c\333 at J<\17\313O\26\23\22\366\0W\307\271\24\313n*\345k\254\246\211\216"..., 4096) = 4096
+read(8, "\265\205d\267\263\324{\354\362\273\v71\251\342\247\367\376&\215,\275\7\216:\317\276z\362I\362g"..., 4096) = 4096
+read(8, "s?{\2731\315?\37\221\224\2220c\215Y\25\362O\240%wl\365\375\262cx\10;\f\3550"..., 4096) = 4096
+read(8, "\0314MiT\374z\3127z:C\317\356\200\36 \353\231\254D|\353\210\260\360\240G\330\373d="..., 4096) = 4096
+read(8, "\335RR\337\"r\342x\220\221U\375H\315J\213\303k-\2518\244\4\vcZ\207\234\327q\270,"..., 4096) = 4096
+read(8, "\223\315x8=?\377\325\374\321>\16\16\316G\v\213\1}\264C\321\354\274<\361\233\344\314\202\2I"..., 4096) = 4096
+read(8, "o\275\226\201\201\301\24\243h\24\317\1\17U\220\2\333\373\344\203G\r\350J\3\32\275\335\17\214C?"..., 4096) = 4096
+read(8, "\355]X\10\313\3qc\2}K\310\235\35\236\266'W\322\341\334$.\322\332y.~w\33\306\26"..., 4096) = 4096
+read(8, "7.pngPK\1\2\24\0\24\0\10\10\10\0\271dhD\306\266\377\31\220.\0\0R2\0"..., 4096) = 1052
+read(8, "", 4096) = 0
+read(8, "", 4096) = 0
+read(6, "\270\3g\2\351\3\352\177\316\333\274\243\1\227\3o\334)*\376(\346\225\346\274\315\353\360\177m\350\203"..., 4096) = 4096
+read(6, "a\320M\275\344x(}\24K.\226C/\215p\374\22?\241\20\7\203\35\277\274u\373\266\320{4"..., 4096) = 4096
+read(6, "\364Je\345\33\201\301\314\275\344+\313\227\350\27t\34~\316?\27\366V\204\273\227\17\375Q`\330\244"..., 4096) = 4096
+read(6, "\26\316\5r\2632<]\34:w\356l}\304QH$\27\tU\27\302:\325U\225\17n\335P\36"..., 4096) = 4096
+read(6, "\245\254\305\334\"\341\376\227\317\333\37\22\222?p[\304\304\304\244\345B\374\305x\235\345\313\247\261\237s"..., 4096) = 4096
+read(6, "\227\16\222\2\350\350ke-\356]\241\242\243\224DG\251vdG\201 \10\302%\2654\332\327\312:"..., 4096) = 4096
+read(6, "~~\376\314\2313e[\376T,\322\3668w\356\\\247N\235\f\r\r\tc\fq\200i\246\350\326"..., 4096) = 4096
+read(6, ")Q{\301?\223\3736Kg\240>\257\326\231\372\3441,7\355\365\3537`\20\275\222U\16\367T"..., 4096) = 4096
+read(6, "f\326\223\307\260\335\177\324k\304\330\361\344J\272A\356\0iY\220\332\302\274w\f\244\226\33\36\"\210"..., 4096) = 4096
+read(6, "\355+7\234G\272\2<\220Z\30\02200\342#\303\26,3\254\251\372\331Wr\300\322\225\246\0205"..., 4096) = 4096
+read(6, "\266\251\273/\236=!KR\\\324\322\r\333\7\217\32\313\254\360s\301\247%\366\26d\200g\226\274L"..., 4096) = 4096
+read(6, "\23\35f\325u\247\324\t\0\305EE'\217\36\214\16\373\317L}\261\341\301\373\267\271\221\277Y\352."..., 4096) = 4096
+read(6, "\230\233\363\232\272\\\302\262i\223\236=\274O=\312<\7\213\363\311X'\374}\375}<Y7q\331"..., 4096) = 4096
+read(6, "\323\fM%\353\32\242\262\3615\0\0\360\27\302%\0\10\30\27\237\220:\227\351\247\245\3156\352Pc"..., 4096) = 4096
+read(6, "\306\326\265\226/\315UzV\365\234\234\317\377\271|\331\323\7QU\333\2326\213\265\220\364\242\240\273/"..., 4096) = 4096
+read(6, ">\351,,,L^5\326\317T\315\2773\363\356\375\260Y\355t\4%\355\360\341\310e\250\337\331\223"..., 4096) = 4096
+read(6, "\245\327\t\247\313\27\375M\226\256\"\205\302\325\227\322\316\375\222\243;w\276\276\331b\266JH\16 \253"..., 4096) = 4096
+read(6, "\222\226\362\356o\207}\302\302m\26X\256\33=iZ\273v\355\237>\210:w\362HB\354K?\367"..., 4096) = 4096
+read(6, "\321\262\231dC\267\2\0@\264\224\224U|\377Q\336\320\255h\202\204\2267\3304\373s\317v\341\257"..., 4096) = 4096
+read(6, "`\324\255\372\361 \352_\3\0\324\37\217+\371\230\352\177\372\243\250\335\307/)\361\266\327\276\235\2572"..., 4096) = 4096
+read(6, "s\363\332\3A\221\324\10P\2]5s\25\202x\343>\346\276ep\234\260\341\177\304\235:\233\205\271"..., 4096) = 4096
+read(6, "K\0h:\250H1c\336R\353\371\313\204\263\306\327Y/\311\343B3\3\256\257\346\345\262\337\377W"..., 4096) = 4096
+read(6, "\251\276\300\332\326O\1K{O2\262\16/\331 \235\16!?;\360A.UNe\210$ZB%"..., 4096) = 4096
+read(6, "\0\0\0\0\0\0\0\0\0\0\17\263\1\0Pictures/100000000"..., 4096) = 1288
+read(6, "", 4096) = 0
+read(6, "", 4096) = 0
+write(7, "\214\355\321\313\214\376\312\312nd\350\275{eee\1\27\2\355\306\214Ul;\260\233b\357\201\203\273"..., 4096) = 4096
+write(7, "\270\3g\2\351\3\352\177\316\333\274\243\1\227\3o\334)*\376(\346\225\346\274\315\353\360\177m\350\203"..., 4096) = 4096
+write(7, "a\320M\275\344x(}\24K.\226C/\215p\374\22?\241\20\7\203\35\277\274u\373\266\320{4"..., 4096) = 4096
+write(7, "\364Je\345\33\201\301\314\275\344+\313\227\350\27t\34~\316?\27\366V\204\273\227\17\375Q`\330\244"..., 4096) = 4096
+write(7, "\26\316\5r\2632<]\34:w\356l}\304QH$\27\tU\27\302:\325U\225\17n\335P\36"..., 4096) = 4096
+write(7, "\245\254\305\334\"\341\376\227\317\333\37\22\222?p[\304\304\304\244\345B\374\305x\235\345\313\247\261\237s"..., 4096) = 4096
+write(7, "\227\16\222\2\350\350ke-\356]\241\242\243\224DG\251vdG\201 \10\302%\2654\332\327\312:"..., 4096) = 4096
+write(7, "~~\376\314\2313e[\376T,\322\3668w\356\\\247N\235\f\r\r\tc\fq\200i\246\350\326"..., 4096) = 4096
+write(7, ")Q{\301?\223\3736Kg\240>\257\326\231\372\3441,7\355\365\3537`\20\275\222U\16\367T"..., 4096) = 4096
+write(7, "f\326\223\307\260\335\177\324k\304\330\361\344J\272A\356\0iY\220\332\302\274w\f\244\226\33\36\"\210"..., 4096) = 4096
+write(7, "\355+7\234G\272\2<\220Z\30\02200\342#\303\26,3\254\251\372\331Wr\300\322\225\246\0205"..., 4096) = 4096
+write(7, "\266\251\273/\236=!KR\\\324\322\r\333\7\217\32\313\254\360s\301\247%\366\26d\200g\226\274L"..., 4096) = 4096
+write(7, "\23\35f\325u\247\324\t\0\305EE'\217\36\214\16\373\317L}\261\341\301\373\267\271\221\277Y\352."..., 4096) = 4096
+write(7, "\230\233\363\232\272\\\302\262i\223\236=\274O=\312<\7\213\363\311X'\374}\375}<Y7q\331"..., 4096) = 4096
+write(7, "\323\fM%\353\32\242\262\3615\0\0\360\27\302%\0\10\30\27\237\220:\227\351\247\245\3156\352Pc"..., 4096) = 4096
+write(7, "\306\326\265\226/\315UzV\365\234\234\317\377\271|\331\323\7QU\333\2326\213\265\220\364\242\240\273/"..., 4096) = 4096
+write(7, ">\351,,,L^5\326\317T\315\2773\363\356\375\260Y\355t\4%\355\360\341\310e\250\337\331\223"..., 4096) = 4096
+write(7, "\245\327\t\247\313\27\375M\226\256\"\205\302\325\227\322\316\375\222\243;w\276\276\331b\266JH\16 \253"..., 4096) = 4096
+write(7, "\222\226\362\356o\207}\302\302m\26X\256\33=iZ\273v\355\237>\210:w\362HB\354K?\367"..., 4096) = 4096
+write(7, "\321\262\231dC\267\2\0@\264\224\224U|\377Q\336\320\255h\202\204\2267\3304\373s\317v\341\257"..., 4096) = 4096
+write(7, "`\324\255\372\361 \352_\3\0\324\37\217+\371\230\352\177\372\243\250\335\307/)\361\266\327\276\235\2572"..., 4096) = 4096
+write(7, "s\363\332\3A\221\324\10P\2]5s\25\202x\343>\346\276ep\234\260\341\177\304\235:\233\205\271"..., 4096) = 4096
+write(7, "K\0h:\250H1c\336R\353\371\313\204\263\306\327Y/\311\343B3\3\256\257\346\345\262\337\377W"..., 4096) = 4096
+write(7, "\251\276\300\332\326O\1K{O2\262\16/\331 \235\16!?;\360A.UNe\210$ZB%"..., 4096) = 4096
+write(9, "\277+7\17\371\5e\275\310\241\334\206\240\0\205\210\34\205\352\10\r\205\347N\200\205\321\30\0\202:\204"..., 4096) = 4096
+write(9, "\35\277\223\21hkk[\266l\331\363\317?oee\365\312+\257\344\347\347\313D\240c\16\352DC"..., 4096) = 4096
+write(9, "\205\32U\273\21.f\320u>Z\260p\377\201\203\255\255\255\337n\337\21\277p\261T*\365\237\370f"..., 4096) = 4096
+write(9, "d\232\307\320\242\202\32\275\32Y\271\205\220\303H;\0m\0\341: \247v\35>\371\373\32\3\217S"..., 4096) = 4096
+write(9, "\v\261%Tc8\215\323\21tw\232\205M\325\205\21\221\371\37\21y\327\256]|n\221mjkk"..., 4096) = 4096
+write(9, " \10\202 \10\371\240&C\20\4A\20\4!\37\324d\10\202 \10\202 \344#UZQ\213\223\306"..., 4096) = 4096
+write(9, "\230:{\36UM\343BLd\372\213\224\342\242\2\325\336\32\377\2318y\336\202%\304\2114\2\221\366"..., 4096) = 4096
+write(9, "\330Bg\27(y9\331\311\367n\335\276r\1\4\250\357\16\327\352\252*\3026\33A\20\4\21$b"..., 4096) = 4096
+write(9, "\260\277\224\264\333\331\10KS\375\350\260\240\351\32\363\225&N\341\225\267\10-2\244A d\205\371\271"..., 4096) = 4096
+write(9, "\343\270\235\365\311\360X\362\31\3478\34\354N\356\266V\377\273\177g\343\236?\377\363\373?W{g-"..., 4096) = 4096
+write(9, "\241j\267\242\312\213\v\v\311\250\274q\351\334\237?\177R\345w\257_&K\372\223G\314\17\220\310P"..., 4096) = 4096
+write(9, "\264g\231i\317.\236\213\361<\34\306\374\236%.<\304\327sS\351\217\37\324\335W\31\351d\271\236"..., 4096) = 4096
+write(9, "`<JZZ\242\3374\n\f[7`\240'\210\25d\217\372\21\211_\340\2\0\360\317\267\257_\316"..., 4096) = 4096
+write(9, "-vG\230\345\332#\307\234\275\34u\353r\310\375[\327\263>e|\371\234UYY\321EAIN"..., 4096) = 4096
+write(9, "\255g\37\241\252a\325\330\257\t\306*\361M\325\0\302\343\246\316d+\37?u&\211nO\356\336\256"..., 4096) = 4096
+write(9, "\267\356!\371i\371\246?\355\266\254\371k\327\226\243~\241\324\230S\274\336:\265\25^t\225\354\254\217"..., 4096) = 4096
+write(9, "\214P\365\230\300z\363\26M\236eH\357\317\310X\5\235u\377\226\373u\356\342\25\346\253\326S%d"..., 4096) = 4096
+write(9, "\257S\236G\35~\2338EA\271k\2756**Z\365FZT\370=,\340\334\322u[\307O\233"..., 4096) = 4096
+write(9, "6\10\227\0\0\0\0@\233f\371Ee\25\r\335\10q#%\301h\323BJ\10+\372R\\\206w"..., 4096) = 4096
+write(9, "r\232\355o\177\307\236\177\371\354\211\247\313v\207-{Y_\212\16\t\270s\355\312\3737\331\16[\367"..., 4096) = 4096
+write(9, "\324\357\330\360\361\223\250\221eD\220\3603%\245\270\250\320m\363\332\212\362\362%\353\266\222\10\265`\365"..., 4096) = 4096
+write(9, "m\247\230['\304\305\204\371\35{\226\374h\357\206\225\307\335\367\370F_\23D\313\2330\341w\3\6"..., 4096) = 4096
+write(9, "\262\342\35u\0344\266\3444\276\313\203\0f\7\307\241d\255\203h\37\5\25\257:\253\237\2U\\\322"..., 4096) = 4096
+read(6, "", 4096) = 0
+close(8) = 0
+write(7, "\0\0\0\0\0\0\0\0\0\0\17\263\1\0Pictures/100000000"..., 1288) = 1288
+close(7) = 0
+close(6) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\240\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741825, len=1}) = 0
+lseek(3, 125952, SEEK_SET) = 125952
+read(3, "\n\0\0\0\20\0\303\0\1\307\1\373\2.\2b\2\226\1\223\0\367\0\303\2\312\2\375\0031\1+"..., 1024) = 1024
+rename("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/tmp/svn-VYI0r2", "/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/pristine/89/89fca0c3d11ef3e57ccef84637945108c9785928.svn-base") = 0
+stat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/pristine/89/89fca0c3d11ef3e57ccef84637945108c9785928.svn-base", {st_mode=S_IFREG|0664, st_size=197896, ...}) = 0
+lseek(3, 162816, SEEK_SET) = 162816
+read(3, "\r\0\0\0\3\2\342\0\3\241\3B\2\342\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(3, 54272, SEEK_SET) = 54272
+read(3, "\2\1\370\0\21\0L\3\0\0\0\215\2S\0\253\0|\1\311\1\231\3p\1j\3\320\0\333\3A"..., 1024) = 1024
+lseek(3, 102400, SEEK_SET) = 102400
+read(3, "\n\0\0\0\23\0\302\0\0\302\0\356\1\32\1E\1q\1\235\1\311\1\365\2 \2L\2x\2\244"..., 1024) = 1024
+stat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db", {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+stat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db", {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+open("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", O_RDWR|O_CREAT|O_CLOEXEC, 0644) = 6
+fstat(6, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
+geteuid() = 530
+fstat(6, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
+lseek(6, 0, SEEK_SET) = 0
+write(6, "\331\325\5\371 \241c\327\377\377\377\377\0265o\343\0\0\0\241\0\0\2\0\0\0\4\0\0\0\0\0"..., 512) = 512
+lseek(6, 512, SEEK_SET) = 512
+write(6, "\0\0\0e", 4) = 4
+lseek(6, 516, SEEK_SET) = 516
+write(6, "\n\0\0\0\23\0\302\0\0\302\0\356\1\32\1E\1q\1\235\1\311\1\365\2 \2L\2x\2\244"..., 1024) = 1024
+lseek(6, 1540, SEEK_SET) = 1540
+write(6, "\0265p\344", 4) = 4
+lseek(6, 1544, SEEK_SET) = 1544
+write(6, "\0\0\0|", 4) = 4
+lseek(6, 1548, SEEK_SET) = 1548
+write(6, "\n\0\0\0\20\0\303\0\1\307\1\373\2.\2b\2\226\1\223\0\367\0\303\2\312\2\375\0031\1+"..., 1024) = 1024
+lseek(6, 2572, SEEK_SET) = 2572
+write(6, "\0265p\353", 4) = 4
+lseek(6, 2576, SEEK_SET) = 2576
+write(6, "\0\0\0\240", 4) = 4
+lseek(6, 2580, SEEK_SET) = 2580
+write(6, "\r\0\0\0\3\2\342\0\3\241\3B\2\342\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(6, 3604, SEEK_SET) = 3604
+write(6, "\0265p\27", 4) = 4
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+lseek(6, 3608, SEEK_SET) = 3608
+write(6, "\0\0\0\1", 4) = 4
+lseek(6, 3612, SEEK_SET) = 3612
+write(6, "SQLite format 3\0\4\0\1\1\0@ \0\0\5\240\0\0\0\241"..., 1024) = 1024
+lseek(6, 4636, SEEK_SET) = 4636
+write(6, "\0265o\343", 4) = 4
+lseek(3, 0, SEEK_SET) = 0
+write(3, "SQLite format 3\0\4\0\1\1\0@ \0\0\5\241\0\0\0\241"..., 1024) = 1024
+lseek(3, 102400, SEEK_SET) = 102400
+write(3, "\n\0\0\0\24\0\226\0\0\302\0\356\1\32\1E\1q\1\235\1\311\1\365\2 \2L\0\226\2x"..., 1024) = 1024
+lseek(3, 125952, SEEK_SET) = 125952
+write(3, "\n\0\0\0\21\0\217\0\0\217\1\307\1\373\2.\2b\2\226\1\223\0\367\0\303\2\312\2\375\0031"..., 1024) = 1024
+lseek(3, 162816, SEEK_SET) = 162816
+write(3, "\r\0\0\0\4\2\202\0\3\241\3B\2\342\2\202\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+close(6) = 0
+unlink("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal") = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=2}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLOUT, {u32=3303153608, u64=140397194125256}}) = 0
+epoll_wait(4, {{EPOLLOUT, {u32=3303153608, u64=140397194125256}}}, 16, 500) = 1
+write(9, "\0\0\0\0\0\0005#\2\0Pictures/1000000000000"..., 1011) = 1011
+lseek(9, 0, SEEK_SET) = 0
+stat("/tmp/svn-4fJ8w7", {st_mode=S_IFREG|0600, st_size=197619, ...}) = 0
+mmap(NULL, 197619, PROT_READ, MAP_SHARED, 9, 0) = 0x7fb0c4143000
+writev(5, [{"PUT /svn/aprx/!svn/wrk/9ce1e70b-"..., 96}, {"Host", 4}, {": ", 2}, {"repo.ham.fi", 11}, {"\r\n", 2}, {"Authorization", 13}, {": ", 2}, {"Basic b2gybXFrOnJpZnJhZjIy", 26}, {"\r\n", 2}, {"User-Agent", 10}, {": ", 2}, {"SVN/1.8.8 (x86_64-redhat-linux-g"..., 46}, {"\r\n", 2}, {"Content-Type", 12}, {": ", 2}, {"application/vnd.svn-svndiff", 27}, {"\r\n", 2}, {"Accept-Encoding", 15}, {": ", 2}, {"gzip", 4}, {"\r\n", 2}, {"DAV", 3}, {": ", 2}, {"http://subversion.tigris.org/xml"... [...]
+writev(5, [{"\206\7\3553V\314\234\370\370\336\235\372r\362%\223%\243W\361W\311\237>d\320\355\317}\373WU"..., 128757}, {"\r\n", 2}, {"0\r\n\r\n", 5}], 3) = -1 EAGAIN (Resource temporarily unavailable)
+epoll_wait(4, {{EPOLLOUT, {u32=3303153608, u64=140397194125256}}}, 16, 500) = 1
+writev(5, [{"\206\7\3553V\314\234\370\370\336\235\372r\362%\223%\243W\361W\311\237>d\320\355\317}\373WU"..., 128757}, {"\r\n", 2}, {"0\r\n\r\n", 5}], 3) = 128764
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN, {u32=3303153656, u64=140397194125304}}) = 0
+epoll_wait(4, {}, 16, 500) = 0
+epoll_wait(4, {{EPOLLIN, {u32=3303153656, u64=140397194125304}}}, 16, 500) = 1
+read(5, "HTTP/1.1 204 No Content\r\nDate: M"..., 8000) = 106
+munmap(0x7fb0c4143000, 197619) = 0
+close(9) = 0
+write(1, ".", 1) = 1
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/aprx-manual.pdf", {st_mode=S_IFREG|0664, st_size=438348, ...}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\241\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+open("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/aprx-manual.pdf", O_RDONLY|O_CLOEXEC) = 6
+open("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/tmp/svn-2LgtUH", O_RDWR|O_CREAT|O_EXCL, 0600) = 7
+fcntl(7, F_GETFD) = 0
+fcntl(7, F_SETFD, FD_CLOEXEC) = 0
+fstat(7, {st_mode=S_IFREG|0600, st_size=0, ...}) = 0
+chmod("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/tmp/svn-2LgtUH", 0664) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\241\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\241\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+open("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/pristine/80/801e13de8f6f628e0af791392a3cdbe991e46277.svn-base", O_RDONLY|O_CLOEXEC) = 8
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\241\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\241\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+open("/tmp/svn-S2P1pi", O_RDWR|O_CREAT|O_EXCL, 0600) = 9
+fcntl(9, F_GETFD) = 0
+fcntl(9, F_SETFD, FD_CLOEXEC) = 0
+read(8, "%PDF-1.4\n%\303\244\303\274\303\266\303\237\n2 0 obj\n<</Le"..., 4096) = 4096
+read(8, "0\223\34\366\25\351NH\7\365\275?\371O(o\231\375\305PiNo\300\\[qBR *\34"..., 4096) = 4096
+read(8, "A\222S\1M&\325\32\21\240\33!\223\3115\23\22\4o\2624>\203\20\253\311\371\0004\305\320\31"..., 4096) = 4096
+read(8, "BB\345\326U\33wX\266o(\376\\9vPf\367^\371o\257+\252>S\361\305W5\327\276"..., 4096) = 4096
+read(8, "\273\310\226\224\27\26\331R-#\357/\274<\355xy\266\264l9z\353'\345\250\263\331\271\223\32\232"..., 4096) = 4096
+read(8, "\31\335z\312\0263\266i\234\fu\345'g\312\17\325\324\315\245\316\320]\277\253|\330\263\317I\177Y"..., 4096) = 4096
+read(8, "\266\37<\361\366\226\242'G\215\r\16\t\221\233RZ\267\251\274|\307\343\201\320\304\32>:\227l\t"..., 4096) = 4096
+read(8, "\277\277\243\304E\217\f\331\22\0\0\370+W\37\337rg\325\351Q\23\246FEGKf\213\210\214\32"..., 4096) = 4096
+read(8, "O]\3\322\206S\n\215Y\36\2579\23\274\245q\344\243\336r\375\266\342\266Sf\355\233B\312\362\202"..., 4096) = 4096
+read(8, "\351ZC\313\217\24\27\36|G\337\fe\330\272\357h\243u\26G\372\177|\217\4Jw\241X\247\274"..., 4096) = 4096
+read(8, "\22r(xK\0\0\0\n\201\267\204\340-!\207\202\267\4\0\0\240\20xK\10\336\22r(xK"..., 4096) = 4096
+read(8, "\265\214\10$\256\305 U\305\324R\263\4>b\325\24\"6\346\230\222\324\2\373\345\303\327\224!\21\5"..., 4096) = 4096
+read(8, "\370\211\354\316\212\212\211\315\330\224\235_Y_v\371n\365\315\207\205g\256c\16\360\336\324Y\255\221\334"..., 4096) = 4096
+read(8, "^i\356\331\246\277Z\221\325X\256\244\225\306X\t=\311`D& \fLD`:\2&\256\245\273"..., 4096) = 4096
+read(8, "@\17XK\210d\234h\220\214qH\260\26\222\201\310\222\10Q1m\304\223\346\326\222\343^\336\336\21"..., 4096) = 4096
+read(8, "p0\224\10\26|\33>*\235,-Ru\343+R2u\301R\207(K#\271(=!\271\217\273"..., 4096) = 4096
+read(8, "-o\225/I\324M\364\345PJ\225o\202YJ\"Y\342(y?\0342\340\374\374\265\356x\203'"..., 4096) = 4096
+read(8, "\245\342\345;(\306\365\305\263{\256\353q\t\3725\343eZ&J\246\207P\223\205>5\213\246\1~"..., 4096) = 4096
+read(8, "\202\202\203\333'\245d<?2{\326\202\375\225\347\34\365wc\277\270=\2417\306\353L\17e\240}"..., 4096) = 4096
+read(8, "ELXx8\255[fv.\367\2133\r\352\312s\323\223*DP\371`z*\5\246\247\357\226w"..., 4096) = 4096
+read(8, "\347\32-0=\16\305K4.\241\25;9\322Y~\325'\333\345\277\337\256\310\371\275\357\340a\324\262"..., 4096) = 4096
+read(8, "^\267Q\357\236x4ta\f\270A\10]F\22B\27\27!t\231V\6\200.\341e\365\235\357\17"..., 4096) = 4096
+read(8, "\21\22\232@\227`J\350K\253+]*S\fK[\256\275\373\203\305~QB6l\300\331\300\204."..., 4096) = 4096
+read(8, "]i\332\\J\320m\350\274\300\f\360\241\264\r\t\303C\227\336a -\240>u\262\233c\200-\204"..., 4096) = 4096
+read(8, "\17u?\37\354\210[\213\5K\200[\2\0\0\0\300V\250\352\226G\217\36\225\251\224\2\226\271\345\371"..., 4096) = 4096
+read(6, "%PDF-1.4\n%\303\244\303\274\303\266\303\237\n2 0 obj\n<</Le"..., 4096) = 4096
+read(6, "\344\226\5r\10\344]\362\367#\225VK%\331~\231\261\372y\314@\341\356\351E\313W\245\252\222\324"..., 4096) = 4096
+read(6, "\334\267l\331\270\361F\f-\345\233ap\254\2279\222\375H\36\322Y u\34\337\254<\316\212ep"..., 4096) = 4096
+read(6, "=~\307\375\262\310\226\36)\262e\300\26\331R\203!\352\376\"[R^XdK\177*\262\245G\212"..., 4096) = 4096
+read(6, "\215\221\214\241\351\246\374c\312\255^r\206M\265\34{m\377\341\207\37<\362\217c\375\\\30L}R"..., 4096) = 4096
+read(6, "\361\3253X~\220-\345]\325\366\203'\236\177q\201\262C\204<b\352M\362O\321\275\357\300\240\372"..., 4096) = 4096
+read(6, "\34\233\375%\277\311\325\232\353?h\246\252\272\362\255\301ly\270\356\236fZe7\37\231\263\\\216\210"..., 4096) = 4096
+read(6, "\314B\266\4\0\0\200Y\310\226\0\0\0000\v\331\22\0\0\0f![\2\0\0\300,dK\0\0"..., 4096) = 4096
+read(6, ";\246\247';\342\216\342B\tn\n\336\222\303;G\23\377\226\27zK\231\337D\4\334\377\340j\225"..., 4096) = 4096
+read(6, "W^\32;qJ\367^\211!\241\226\204\244~\354f\221d\2014F'\17L\243a:\274C\304\340"..., 4096) = 4096
+read(6, "Q$<\350\245/\264\20E\273\240b\216\221\31/\275\370\361\205.E\266\263\304\27\376\20I\244\265B"..., 4096) = 4096
+read(6, "\255\2640\355\322\317\277\264/KW\337z\324\321\317\237\33\6\272w\245Z0\246\245\20\256\317\264\2a"..., 4096) = 4096
+read(6, "aZ\262MN\374R\263\302z-\312'L\231aj\231\300|\276u\327\341\332K\230iW\337|H"..., 4096) = 4096
+read(6, "\310\v\221\314\223\204Hf\212\20\311l+\17F2P\306\354EPa\372\342\34\231&\31s\26\v&"..., 4096) = 4096
+read(6, "\213\342\213 \331\337\376\3667\375F\2372\270\3\354\327_\177M\16\316z \31L\240dE\21/o"..., 4096) = 4096
+read(6, "D2\333\312\236HFTTs)}\312\254\300\240\347\251\353\374\3\2\3233g\26\237\276\302=\253\6"..., 4096) = 4096
+read(6, "\330\f\327\311~\326\25:\"\224w=S\263\5n1\236\213\"\353,\376\217W\27\0254y\217\352?"..., 4096) = 4096
+read(6, "\262m\227Rw\357\361\313\23\247\312L\365\312\364\327\344M\317\3259\300\364\0\0\0\0`L\0241\275"..., 4096) = 4096
+read(6, "\v\275\246\310\2344\255mb\22\271A\277!\303\227\257^\317-\264\344\354\2151\223s\273\244\367\244E"..., 4096) = 4096
+read(6, "\305\27\307\33\356\221E\364\31\364\34\275\0a\37\305.\\\361W\366\215D\r\352\312\303\nq\343\231A"..., 4096) = 4096
+read(6, "\355\211I\2063\367\244H\223\31\244'\33/]8\365\203\355\251\213\327f8\233\236.\203\357i\2147"..., 4096) = 4096
+read(6, "\360\227\24\226\7\275\265\352}x\267\377\213#(\377\312\277\177B*\216\34;\376\243\375\205\324\35\274J"..., 4096) = 4096
+read(6, "\241\v\305\226\7AW\355\335\37,\366_\226e\307^p6\3503\332;\202\256\262\353-$\3638e"..., 4096) = 4096
+read(6, "h 57 0 R\n/Filter/FlateDecode/Col"..., 4096) = 4096
+read(6, "aC18h\\\307-Wn\330\254\354\233\242\376\341\217A!\241\16\372vk\202[\32`\256[\322"..., 4096) = 4096
+write(7, "%PDF-1.4\n%\303\244\303\274\303\266\303\237\n2 0 obj\n<</Le"..., 4096) = 4096
+write(7, "\344\226\5r\10\344]\362\367#\225VK%\331~\231\261\372y\314@\341\356\351E\313W\245\252\222\324"..., 4096) = 4096
+write(7, "\334\267l\331\270\361F\f-\345\233ap\254\2279\222\375H\36\322Y u\34\337\254<\316\212ep"..., 4096) = 4096
+write(7, "=~\307\375\262\310\226\36)\262e\300\26\331R\203!\352\376\"[R^XdK\177*\262\245G\212"..., 4096) = 4096
+write(7, "\215\221\214\241\351\246\374c\312\255^r\206M\265\34{m\377\341\207\37<\362\217c\375\\\30L}R"..., 4096) = 4096
+write(7, "\361\3253X~\220-\345]\325\366\203'\236\177q\201\262C\204<b\352M\362O\321\275\357\300\240\372"..., 4096) = 4096
+write(7, "\34\233\375%\277\311\325\232\353?h\246\252\272\362\255\301ly\270\356\236fZe7\37\231\263\\\216\210"..., 4096) = 4096
+write(7, "\314B\266\4\0\0\200Y\310\226\0\0\0000\v\331\22\0\0\0f![\2\0\0\300,dK\0\0"..., 4096) = 4096
+write(7, ";\246\247';\342\216\342B\tn\n\336\222\303;G\23\377\226\27zK\231\337D\4\334\377\340j\225"..., 4096) = 4096
+write(7, "W^\32;qJ\367^\211!\241\226\204\244~\354f\221d\2014F'\17L\243a:\274C\304\340"..., 4096) = 4096
+write(7, "Q$<\350\245/\264\20E\273\240b\216\221\31/\275\370\361\205.E\266\263\304\27\376\20I\244\265B"..., 4096) = 4096
+write(7, "\255\2640\355\322\317\277\264/KW\337z\324\321\317\237\33\6\272w\245Z0\246\245\20\256\317\264\2a"..., 4096) = 4096
+write(7, "aZ\262MN\374R\263\302z-\312'L\231aj\231\300|\276u\327\341\332K\230iW\337|H"..., 4096) = 4096
+write(7, "\310\v\221\314\223\204Hf\212\20\311l+\17F2P\306\354EPa\372\342\34\231&\31s\26\v&"..., 4096) = 4096
+write(7, "\213\342\213 \331\337\376\3667\375F\2372\270\3\354\327_\177M\16\316z \31L\240dE\21/o"..., 4096) = 4096
+write(7, "D2\333\312\236HFTTs)}\312\254\300\240\347\251\353\374\3\2\3233g\26\237\276\302=\253\6"..., 4096) = 4096
+write(7, "\330\f\327\311~\326\25:\"\224w=S\263\5n1\236\213\"\353,\376\217W\27\0254y\217\352?"..., 4096) = 4096
+write(7, "\262m\227Rw\357\361\313\23\247\312L\365\312\364\327\344M\317\3259\300\364\0\0\0\0`L\0241\275"..., 4096) = 4096
+write(7, "\v\275\246\310\2344\255mb\22\271A\277!\303\227\257^\317-\264\344\354\2151\223s\273\244\367\244E"..., 4096) = 4096
+write(7, "\305\27\307\33\356\221E\364\31\364\34\275\0a\37\305.\\\361W\366\215D\r\352\312\303\nq\343\231A"..., 4096) = 4096
+write(7, "\355\211I\2063\367\244H\223\31\244'\33/]8\365\203\355\251\213\327f8\233\236.\203\357i\2147"..., 4096) = 4096
+write(7, "\360\227\24\226\7\275\265\352}x\267\377\213#(\377\312\277\177B*\216\34;\376\243\375\205\324\35\274J"..., 4096) = 4096
+write(7, "\241\v\305\226\7AW\355\335\37,\366_\226e\307^p6\3503\332;\202\256\262\353-$\3638e"..., 4096) = 4096
+write(7, "h 57 0 R\n/Filter/FlateDecode/Col"..., 4096) = 4096
+write(9, "SVN\0\0\206\240\0\206\240\0H\201\3418\0I\0\200\312\n\0\201\362`\321\0\200\236}\0\253"..., 4096) = 4096
+write(9, "\330\321\336\335`\f\343\245\17\271=\30\310!\344\226\5r\10\344]\362\367#\225VK%\331~\231\261"..., 4096) = 4096
+write(9, "\205j\324\250\234\244Ug\344\240PR-4\334\267l\331\270\361F\f-\345\233ap\254\2279\222\375"..., 4096) = 4096
+write(9, "\253\260\10\354\344%\261\220$\0014#\250\321\0\2157\32m5\242\334\234\245q\26;m\177\"\0r"..., 4096) = 4096
+write(9, "u\302\214k<7f\206=,\247f(\320\354\251\263\21c;\233\325\315\233\347\216r\1774\246\330\17"..., 4096) = 4096
+write(9, "\37\31S\246cp\324\36yN\325\2315n\244\344H\250%\242}\352\1\324\377\345\251=l\334W\235"..., 4096) = 4096
+write(9, "W\357\3705\\\266\200\225\267\3[\207\2723h\2\3068\277\347\377\0\3{\6\264\213\231\201\375\2023"..., 4096) = 4096
+read(8, "\267\34?i\332\276\323\225F\347*\330}X\250\366\341\336?\330\374Y\230\212s\270%7\n\207DM"..., 4096) = 4096
+read(8, "\270\364\312\275I\2632c\23\222h\233C\302\302\373'\17]U\260\215\33:I\251h\351\226\302\17\342"..., 4096) = 4096
+read(8, "\240\37N\334\267c\347\332[\340\226\34f\271\245\30\17\17\217\311\351s\313\232[\r\353[\260_\354\307"..., 4096) = 4096
+read(8, "\2520\\\232\251\376A\\^y\353)\375\3373\272w\303\243\27\251\31YA!\241\264L\241\346\316\23"..., 4096) = 4096
+read(8, "<\261\200B\35\r\2616\302I\321\232cF^\220\22%\371\353\270$I\0\376\305$\254j&/`"..., 4096) = 4096
+read(8, "G\21\340r)Y2\207U\21_v,\255\307\362\254\351\307Ku\237yd\\6\207{1Y\272#"..., 4096) = 4096
+read(8, "\272H\325\337^\243\313\"\365\202Z\271\357Vo/\265\241p\r\224\32\206\263\213\353s`\265\tX\326"..., 4096) = 4096
+read(8, "\343\303-\216DC\"]\271\261\25T\4\23\250\341\30\276\222 \205\355,\6\251a$h\4\225;u"..., 4096) = 4096
+read(8, "S;q4\307\221c\227\243^\272\200\205gB\326G\n\262r\234\351NG\222\242\357H+\227\240T"..., 4096) = 4096
+read(8, "5\277\353\225\264h\216\311-5\212U\n1\226E\1\2232\277j\v\204$\320\253\4\204!Of\221"..., 4096) = 4096
+read(8, "p\0T\331\307\271\325\3715\31\247\323\353\31x]h\1\303\215\0007`w\301 B\351\361\215\366\212"..., 4096) = 4096
+read(8, "|\r\334\222\6\314\2?\274\315\"\264\224o\6\307\225\335\332p\315cQ<\224\21\20\225\24\327\22\232"..., 4096) = 4096
+read(8, ":\202\262ERV\272~\2\362\241\310\306L\324\312\222gf\242\1\26o\234\252\02169\236\355O\301"..., 4096) = 4096
+read(8, "$\374Y\334\333\225\371 $\17N\336(\242\37E\357\376\220]\\\336\262\236\314-\254\31`\226\332\t"..., 4096) = 4096
+read(8, "\341S\t\31/K\265ms\261N{\20\215\201\16f\331r\310sk\213T\325n&\224m\224\305+"..., 4096) = 4096
+read(8, "\275\17\321T/\t\320\373\275\35\322\373:g\262\306w\220\265\245\367\25~\244\352\251\32\250\37\227A\347"..., 4096) = 4096
+read(8, "n\332vrj3?\v6qM\274\24\4f?(\324o\301M\351@\334\213\343&:\276\242\315\245"..., 4096) = 4096
+read(8, "\324\230\330I\233\315\256\357\244M\244\361mw\216\264\\H\336\3033\313A\244R\211Xb\266\21\3678"..., 4096) = 4096
+read(8, "u\231\211*\3671\322\361\261\215\200\344z<CI\243f\361\305\2S\337\260\26z\326\366\235\331\n\32"..., 4096) = 4096
+read(8, " obj\n<</Length 2233 0 R/Filter/F"..., 4096) = 4096
+read(8, "\260\3648\222\203\33\246fw\370\n\311\323\r\346@\324C\236\270\10\275\02227\345\360\217\vy\n\213"..., 4096) = 4096
+read(8, "y%?\310\20\334\265\252'T\233\333\203\331C\303\355\201\212\351[\326\4\364@\33\253\20&\373^\227"..., 4096) = 4096
+read(8, "Q\311\242\302J\32\243v\321\367\250~\274\347Bh\372\210\340\361U\320\206\235\245P>\264\252\37\253\7"..., 4096) = 4096
+read(8, "\302g\vrTu9bh0\v9\252\23\362\303\322\vG\314e\264\4D1\212\205N\2\2\221\302"..., 4096) = 4096
+read(8, "\227+\323\204[\374\2563\276\t\353\5:X\320\201ut\0F\25U\301\206\211\337\205\365\255\337B)"..., 4096) = 4096
+read(6, "\337U\270N\274`\367aa\277|\270\367\17\26/\231\225\10?R\267[\323\262\246b\264A\32]\313"..., 4096) = 4096
+read(6, "\242\34*\241\00347cVn>\225/\310}\227\23375#\313\324\306\323v\322\326Ft\375\3070"..., 4096) = 4096
+read(6, "\272~QM3\0335}\373\321\22\v6{\317\251\n\2327\274sW\213\267\312p\tV\6n\311\241"..., 4096) = 4096
+read(6, "\346F%q\375\7p#S5\265\367\233\370\2425\353\271Y6\356< T;Y\177c\374\244i="..., 4096) = 4096
+read(6, "c\34\205\2234\276J\37e\230\376\4\316)\2\370\374\"\257\257o\213\277Z\227\36=\223xl\322\352"..., 4096) = 4096
+read(6, "\204\263\262\240\266\230\22\327\234@\1\262\0\202\365\33\267\326\3!l\5\371\n\236\16\200\2248\24\334\263"..., 4096) = 4096
+read(6, "\365y\1\201\357^\377a*\f\254C\36PU\3407\241TBGZ\305\322\250A\n\3l\325=\\"..., 4096) = 4096
+read(6, "\1T>\220\360\304\4Fh\372\363\250X\336G=*R\327=\253\217\v\307KA\315e\10\334\274O"..., 4096) = 4096
+read(6, ":\351\303\257_\377=\250\203J\237\374\352O\346\2608}Z\16\277\374\370\365\227\337\35\376\363\245N\253"..., 4096) = 4096
+read(6, "9\346\tA>/y\262w\210\310\321\336J\341\247\257\254\374\240\355 \3\310\7\235\341\233}1\200,"..., 4096) = 4096
+read(6, "R\371*\333\217L\377f?M\365\25\246\321\266\362n\270s\321q\342x\317P\353QB\21%\30\266"..., 4096) = 4096
+read(6, "ter/FlateDecode>>\nstream\nx\234\255ZM\2173"..., 4096) = 4096
+read(6, "\353\t\231\220\376\2\10D\217\337\206\300\16\215\37\f\1\303\361\213\20\330\221\21\221\323\214\357q\22%\303"..., 4096) = 4096
+read(6, "\327\364'6\274\326\322\347\276!\212\217=\277u\276\357\231:\257\3452G\323Hb\203\324\24+\241\256"..., 4096) = 4096
+read(6, "\36GT\315SA\341\362\320<\215\3356\327r\2\303\300\230\207\f\322\4i\306fu\250\1\n\tz"..., 4096) = 4096
+read(6, "+\315\222\2715Z\177\250\2\27\323Ac\243\202!\5\325\347\356\221bcS}*\207\25\337\263\266\323"..., 4096) = 4096
+read(6, "Fz\356\202\254s\27\331n&\24\317\331\323\212C\n\215\313\350\25&9\4\20/\310s\302\342v\265"..., 4096) = 4096
+read(6, "\260\333\3714MZ\273]x\303\361\10\20\2029\0341\344\216\303-\333\312I\265\33\265%\252\315y\31"..., 4096) = 4096
+read(6, "\351\224\255\306\252\234\255\353Y\204~Q5t\277\177d\206\315)hc\0262\301\37\6\262\r\205\367\341"..., 4096) = 4096
+read(6, "\243\374&\245\255\342\342d\323\353\225\30\335\327\251Z\311\36\264\5q6\321\352'\212\203\23\256\224\233\344"..., 4096) = 4096
+read(6, "\26\315\333?e\21\33\4\340\323\177\"^*\334\227\351?^\221\350\343\nendstream"..., 4096) = 4096
+read(6, "Filter/FlateDecode>>\nstream\nx\234\255Y"..., 4096) = 4096
+read(6, "\277\276|Z\251\326\312\36\355\206j\346$3)\225\33 \252\2e\">\333\23\231F\223-\235{J"..., 4096) = 4096
+read(6, "lter/FlateDecode>>\nstream\nx\234\255X\313\252"..., 4096) = 4096
+read(6, "5\nendobj\n\n158 0 obj\n<</Length 15"..., 4096) = 4096
+write(7, "aC18h\\\307-Wn\330\254\354\233\242\376\341\217A!\241\16\372vk\202[\32`\256[\322"..., 4096) = 4096
+write(7, "\337U\270N\274`\367aa\277|\270\367\17\26/\231\225\10?R\267[\323\262\246b\264A\32]\313"..., 4096) = 4096
+write(7, "\242\34*\241\00347cVn>\225/\310}\227\23375#\313\324\306\323v\322\326Ft\375\3070"..., 4096) = 4096
+write(7, "\272~QM3\0335}\373\321\22\v6{\317\251\n\2327\274sW\213\267\312p\tV\6n\311\241"..., 4096) = 4096
+write(7, "\346F%q\375\7p#S5\265\367\233\370\2425\353\271Y6\356< T;Y\177c\374\244i="..., 4096) = 4096
+write(7, "c\34\205\2234\276J\37e\230\376\4\316)\2\370\374\"\257\257o\213\277Z\227\36=\223xl\322\352"..., 4096) = 4096
+write(7, "\204\263\262\240\266\230\22\327\234@\1\262\0\202\365\33\267\326\3!l\5\371\n\236\16\200\2248\24\334\263"..., 4096) = 4096
+write(7, "\365y\1\201\357^\377a*\f\254C\36PU\3407\241TBGZ\305\322\250A\n\3l\325=\\"..., 4096) = 4096
+write(7, "\1T>\220\360\304\4Fh\372\363\250X\336G=*R\327=\253\217\v\307KA\315e\10\334\274O"..., 4096) = 4096
+write(7, ":\351\303\257_\377=\250\203J\237\374\352O\346\2608}Z\16\277\374\370\365\227\337\35\376\363\245N\253"..., 4096) = 4096
+write(7, "9\346\tA>/y\262w\210\310\321\336J\341\247\257\254\374\240\355 \3\310\7\235\341\233}1\200,"..., 4096) = 4096
+write(7, "R\371*\333\217L\377f?M\365\25\246\321\266\362n\270s\321q\342x\317P\353QB\21%\30\266"..., 4096) = 4096
+write(7, "ter/FlateDecode>>\nstream\nx\234\255ZM\2173"..., 4096) = 4096
+write(7, "\353\t\231\220\376\2\10D\217\337\206\300\16\215\37\f\1\303\361\213\20\330\221\21\221\323\214\357q\22%\303"..., 4096) = 4096
+write(7, "\327\364'6\274\326\322\347\276!\212\217=\277u\276\357\231:\257\3452G\323Hb\203\324\24+\241\256"..., 4096) = 4096
+write(7, "\36GT\315SA\341\362\320<\215\3356\327r\2\303\300\230\207\f\322\4i\306fu\250\1\n\tz"..., 4096) = 4096
+write(7, "+\315\222\2715Z\177\250\2\27\323Ac\243\202!\5\325\347\356\221bcS}*\207\25\337\263\266\323"..., 4096) = 4096
+write(7, "Fz\356\202\254s\27\331n&\24\317\331\323\212C\n\215\313\350\25&9\4\20/\310s\302\342v\265"..., 4096) = 4096
+write(7, "\260\333\3714MZ\273]x\303\361\10\20\2029\0341\344\216\303-\333\312I\265\33\265%\252\315y\31"..., 4096) = 4096
+write(7, "\351\224\255\306\252\234\255\353Y\204~Q5t\277\177d\206\315)hc\0262\301\37\6\262\r\205\367\341"..., 4096) = 4096
+write(7, "\243\374&\245\255\342\342d\323\353\225\30\335\327\251Z\311\36\264\5q6\321\352'\212\203\23\256\224\233\344"..., 4096) = 4096
+write(7, "\26\315\333?e\21\33\4\340\323\177\"^*\334\227\351?^\221\350\343\nendstream"..., 4096) = 4096
+write(7, "Filter/FlateDecode>>\nstream\nx\234\255Y"..., 4096) = 4096
+write(7, "\277\276|Z\251\326\312\36\355\206j\346$3)\225\33 \252\2e\">\333\23\231F\223-\235{J"..., 4096) = 4096
+write(7, "lter/FlateDecode>>\nstream\nx\234\255X\313\252"..., 4096) = 4096
+write(9, "\336\370\244A\2127B\r\372\0371\277\372\325\257V\256\\\231\233\233\253\331\32\t\362\207\213\327Z\24\274"..., 4096) = 4096
+write(9, "%\vB\253\363\324\325`\300Q\3\215;\21m\244\365\203\210\346\226e\3\23\22\201,\347_\333\262C"..., 4096) = 4096
+write(9, "&\27\23\273\v\2\335\5\223\36Lg\25\3\301\357\347\35\\Nc\5\207\\\316\17~T9B\v;"..., 4096) = 4096
+write(9, "]\333kU\376`\227e\261\313\222\26\261u\2\36\232z\255\241\225\24\254\36\253$\325=N\353j\275"..., 4096) = 4096
+write(9, "\35i\37\254\307\264&\214\337\303\2\177\234s\242d\207\305\325^\366'\305\325\314\201\376\266lw\202\241"..., 4096) = 4096
+write(9, "\0\202\3525\n\212\10\325-\334X\371\306\35\347L\300UG\234\307\23\231\205gc\250\302\224\v\216\0"..., 4096) = 4096
+write(9, "A\356\214\21\215\316\r\32yld%\30\3322\"\23\224\205\22\31%\310!GK\333\306\20Q\205;"..., 4096) = 4096
+write(9, "\31\373lQ\365\234\274z\366\206\365b\323y\336\305\234{\244\331A\367\255m\272z\244\365\226v`\207"..., 4096) = 4096
+write(9, "\3~\216;\373\217+\0K\334\24\301\362\371\310*a\3008\251Zb\364\227\26\270Hh\311\266\351e"..., 4096) = 4096
+write(9, "@\362Z\273i+!\274\215\324\v$\231[s\300\235\230\303\333\222f%\35?\307\226\207l\34h\267"..., 4096) = 4096
+write(9, "5\370\325Wo\304\275Y\216\203ra\177\23F\r\377\262}!\212\202\3508\177\rPb\303\2366C"..., 4096) = 4096
+write(9, "/\346q3dw\342*?nAA\303\263\363\202d\271\326L|r\36E\271\254\315#\231\250\"5"..., 4096) = 4096
+write(9, "a<\246\327T\372\223\352IR?yi\334\273\327%\3330\356_\32w\372/M\337_\2326;\207"..., 4096) = 4096
+write(9, "\316\5\205E\7\271b\267\344\25l\365x\254\6m*\246,\236\26\212\320\365\365\254eW\223-4\344"..., 4096) = 4096
+write(9, "{\3\227\313\32\276P\301\210\232\33F\32\350\214\252\305Q8\36\267\35\215\225\342\242\25~\267\"\333u"..., 4096) = 4096
+write(9, "l\321\275n/\277\375\372\355\317\177x\371\27}\7\377~\373\307\267\373\367o\336\271\327\370\262\256\361\345"..., 4096) = 4096
+write(9, "\316\310\207#\315\265\257\273\235\304\375\361\31\2221\354\214s%(\222\373\334[rg\331\25w0;\343"..., 4096) = 4096
+write(9, "\324\236\304\0-\343T&\275&\371B\206\203\34>;\201\223{\23WzV\205\262\f\234\315Y\21o"..., 4096) = 4096
+write(9, "U3\3525\311R\262\304\5\ntYX\244\354\364\334\330(\233>\232\322\324\230\315\236El\355-M"..., 4096) = 4096
+write(9, "m\24<\2671:\305\323\250\"K\232\16\222O0\343\34r3\346H\227\221l\v\207\270\222\205Z\22"..., 4096) = 4096
+write(9, "\311A\2250St\232\\\n\25Uc,Q\311U\202\271TJ\214\33\372z\232\n\226$L_Hm"..., 4096) = 4096
+read(8, "\256\272$\244g\214.5aa\320/-Z\247F~z\f\7\264\2329\v\237\f\304\270J\214\340\313"..., 4096) = 4096
+read(8, "\317\257r\1T\272\332J8\264\3203\230u\241E9\303i`c\341a\200\200\6\235y\200\20\"\216"..., 4096) = 4096
+read(8, "\343\220\261B9%\316r\364t\234\310\306z\301\33\317\246\313\302\2426\352\212\3613\217\312\262b\271r"..., 4096) = 4096
+read(8, "\26\217(\373\231\360\2707{T\254\200\202\240\21\234\25\353\206{\245\viq\356\226\34\v\34\370\212\34"..., 4096) = 4096
+read(8, "\34\343Wz\304\364\323sS\30\307\243\t\372bt\260\300\350c\26\320'C!\351!b\245]\243\246"..., 4096) = 4096
+read(8, "\333+B\306+%\177\313\263U(\301)\244h\ng\215\302\r\334|\324m\240\205\3\21E\30\274\231"..., 4096) = 4096
+read(8, "\257\335\235\211\26v\222\314\23X\332\4\231,\33K\260\351\237\315\265\234\212k\234\21\216\255\370\177\235\354"..., 4096) = 4096
+read(8, "|\361\366\326\362\251s\26\272UODr\352\306\245q\372\275\f\30\34\274\354\346\376\3\30439kJ"..., 4096) = 4096
+read(8, "\216\213\362\232_YSS\223\271j\371\306r1\366\310\321\177=\375UUU\205\270c\212A\273L\7"..., 4096) = 4096
+read(8, "xS\226F\10!\361\0026\264\356\355-'?\377\\\330\226\3414\273D\244\244l3\362 \247\375\n"..., 4096) = 4096
+read(8, "L\267X?\4k\227\321<aun\374E\277o\244]\6^\10\3552g\241]\352\254%\26)t"..., 4096) = 4096
+read(8, "200000d>\320.\31\30\30\30\30\30R\6M\273\314\26\365\352\325\313v\25\10!\204\220"..., 4096) = 4096
+read(8, "\332`Vu\365\325W\243&\35:tx\374\361\307\341\5w\335u\27\246\264b\251x\215\342\234s\316"..., 4096) = 4096
+read(8, "\320\236\276X\241\255u\215\253\344u;\17&'\353_~En\362\307e_\272?{\371\212|z\365"..., 4096) = 4096
+read(8, "\v\356\222\336'<\371\336K\353\241\301G\17\35\341\361\342\36\243\303\212\315\257\371\310-\237\34\342\302."..., 4096) = 4096
+read(8, "\264\\\276S\371P*\226\305\"\210Sc\361\344\202\6\321E\364\215\270\20\2\6 at G\36&|#\332"..., 4096) = 4096
+read(8, "\261\376wMg1\301{\325&\265\305\2446uP\334\232\31\237Y\233e\206\377\357\v\35\2427\311\301"..., 4096) = 4096
+read(8, "GU)q7=JS\215\30\377\235\v\277\342\2\237\363\271\250o{\360_z\260\313\343\362\224\25\314"..., 4096) = 4096
+read(8, "\320q\374N\177\177\354o\313\210\5\6\260\347d\207\203\356(\352\333:\303B=\24\204\201\30\216\374\247"..., 4096) = 4096
+read(8, "\225\314$\355M\"[\23\367'\222m\232;5d\207zNM\352I\33\31$L:h5e\303\7"..., 4096) = 4096
+read(8, "\16\263<\346yf\213r\37pH\267\2362B\261\311\22\334\245?\254'H\217\365\372\244\270\250\221x"..., 4096) = 4096
+read(8, "`H'[W\241D>\221$\322m\225\214\34j\37]\20\344\226\344`\242\304dJJ\2455\25x"..., 4096) = 4096
+read(8, "m\323m\250\2334\326\3759e\313\262\\\345\313\335n\225\245\276\375\357\336K\305\226\343\251\371Y\217s"..., 4096) = 4096
+read(8, "$\360+\326\360+\337b\345j\177\200u}\371\261g_\317\232\37{\366\261g\327-kY\325\322\261"..., 4096) = 4096
+read(8, "\322\6\337\237\206\357\331\360\335V\370\316>\370\366a\370g\33\276\205\340\337\332\7\217?6\240<\276\17"..., 4096) = 4096
+read(6, "/\31`}8\272\327\f\340\344s\232\301g\256\332-\1sY6z&\32\245\252$\300UI\234J"..., 4096) = 4096
+read(6, ":\0\360%\0\232\306\366\307\341?\233s\2705\nendstream\nendobj"..., 4096) = 4096
+read(6, "\24[O\31\177F\335\222\262\361\210\24\2067L\360\210O\3E\256,7\250G\213\224\271L\360\223\372"..., 4096) = 4096
+read(6, "\231\213 \215N\213Yfi_\224)N.\272P\253w\315j\"\364<\314\n\r\351\212T^\274U"..., 4096) = 4096
+read(6, "\326>\20\25\21\234\21\210\210\263\306\\\302\262\345\257\2127\3130\205\304\271\217\277\216[\2319g\366}"..., 4096) = 4096
+read(6, "\21\251\252\252B\3341\305\240]\272\340\330Ek\376'\273\\h\201\31\370b\203\332t\"\"\262\317\17"..., 4096) = 4096
+read(6, "\25\2112`\342\257\330\234\375~\231\242(\267Z\2515q+\23\22=?\363\5\375}W\357\221\343\305"..., 4096) = 4096
+read(6, "\250`\355R\7\235y\267\316\322\240\210\224]\6\270b\272y\24:x#\303v\31\257\336,p\326\224"..., 4096) = 4096
+read(6, "M5Ep,*e\255\324z*\312|\370\221G\314\327\333\325[I\271G\350\237-\27\0o\270\271"..., 4096) = 4096
+read(6, "c\335\307\36{\314\242C\343\306\215\277\365\255oa_\314\265\322,?\20\5\314)\370i\236\23\245\265"..., 4096) = 4096
+read(6, "$\20)\34\277\5\341\255\34\377\225q+V\375\365\3320\232D\6.\360\232\323\203\262\313&\371\3150"..., 4096) = 4096
+read(6, "\17\260i\17\266\273\t\350\7X\215\1x^\334\362\212|\360\331{\377E|\317\205kK}w\213\201"..., 4096) = 4096
+read(6, "\213\233~*,\374\307\241^\237\7B<\331!\4;<\177\37~\232q@\32\236\337\177\235\0\201\316"..., 4096) = 4096
+read(6, "f\252pD+\233\231\257]\360\207\317u\310Q\373R\314\342\2341\271*\254\242\252\3519\265\270[\30"..., 4096) = 4096
+read(6, "6l\2164+\233\227Ade\223\22i\206MM0`\303\306i\10OC\277\r}\376\265J\337\f"..., 4096) = 4096
+read(6, "4\211n\225\325\340\255@\325\373\321\0264I\212Q;\372#\32\303?\2079\324pz\374T\250\266\263"..., 4096) = 4096
+read(6, "\373\237\34\335y:\231\341tZ5y\232(\265:\371\267r\26\320o\4O\371\232\234\352m\255\313-"..., 4096) = 4096
+read(6, "\203\r\32\203&\205\373\204\7Ts\2117 ..R\245q\221\202\236\23B%\5u\2450X|c"..., 4096) = 4096
+read(6, "\343\23\235\207\357\272\vU9\32\243\5\255\35\321^Ggct\0\22!\232\230\206\204\340\270`AU"..., 4096) = 4096
+read(6, "\327\272k\273\203\301\241x\213]\257c\276\374\331\324\273O\217\327\317~v\327C\357=>^\273\347\241"..., 4096) = 4096
+read(6, "\3057=\344\235L\375\3623j\203^\313\202\201Uk\264,W\320\332\331\0371\344\34595\350\374\352"..., 4096) = 4096
+read(6, "\340,\327\302\334\203\322qZ\356A\f3|\234\207\263Hx\231\223\2\257\20^K\310\327ip17"..., 4096) = 4096
+read(6, "\375\1L,\4\10\246\26\346\347\3+\305\3630O\346\367+\357\225w\363\201\364\v\4Q\312\260p>"..., 4096) = 4096
+read(6, "\321\232\307\326{z\2264&3\23\227\206/q\2\332,x\361\336\343\233\6\16\365\26\206?\3300\342"..., 4096) = 4096
+read(6, "w\274(Y\23\274Lce\262\225Y\35\214\345\321P\34\223\253\306\335\270\206\343M5\2475,\240\301"..., 4096) = 4096
+write(7, "5\nendobj\n\n158 0 obj\n<</Length 15"..., 4096) = 4096
+write(7, "/\31`}8\272\327\f\340\344s\232\301g\256\332-\1sY6z&\32\245\252$\300UI\234J"..., 4096) = 4096
+write(7, ":\0\360%\0\232\306\366\307\341?\233s\2705\nendstream\nendobj"..., 4096) = 4096
+write(7, "\24[O\31\177F\335\222\262\361\210\24\2067L\360\210O\3E\256,7\250G\213\224\271L\360\223\372"..., 4096) = 4096
+write(7, "\231\213 \215N\213Yfi_\224)N.\272P\253w\315j\"\364<\314\n\r\351\212T^\274U"..., 4096) = 4096
+write(7, "\326>\20\25\21\234\21\210\210\263\306\\\302\262\345\257\2127\3130\205\304\271\217\277\216[\2319g\366}"..., 4096) = 4096
+write(7, "\21\251\252\252B\3341\305\240]\272\340\330Ek\376'\273\\h\201\31\370b\203\332t\"\"\262\317\17"..., 4096) = 4096
+write(7, "\25\2112`\342\257\330\234\375~\231\242(\267Z\2515q+\23\22=?\363\5\375}W\357\221\343\305"..., 4096) = 4096
+write(7, "\250`\355R\7\235y\267\316\322\240\210\224]\6\270b\272y\24:x#\303v\31\257\336,p\326\224"..., 4096) = 4096
+write(7, "M5Ep,*e\255\324z*\312|\370\221G\314\327\333\325[I\271G\350\237-\27\0o\270\271"..., 4096) = 4096
+write(7, "c\335\307\36{\314\242C\343\306\215\277\365\255oa_\314\265\322,?\20\5\314)\370i\236\23\245\265"..., 4096) = 4096
+write(7, "$\20)\34\277\5\341\255\34\377\225q+V\375\365\3320\232D\6.\360\232\323\203\262\313&\371\3150"..., 4096) = 4096
+write(7, "\17\260i\17\266\273\t\350\7X\215\1x^\334\362\212|\360\331{\377E|\317\205kK}w\213\201"..., 4096) = 4096
+write(7, "\213\233~*,\374\307\241^\237\7B<\331!\4;<\177\37~\232q@\32\236\337\177\235\0\201\316"..., 4096) = 4096
+write(7, "f\252pD+\233\231\257]\360\207\317u\310Q\373R\314\342\2341\271*\254\242\252\3519\265\270[\30"..., 4096) = 4096
+write(7, "6l\2164+\233\227Ade\223\22i\206MM0`\303\306i\10OC\277\r}\376\265J\337\f"..., 4096) = 4096
+write(7, "4\211n\225\325\340\255@\325\373\321\0264I\212Q;\372#\32\303?\2079\324pz\374T\250\266\263"..., 4096) = 4096
+write(7, "\373\237\34\335y:\231\341tZ5y\232(\265:\371\267r\26\320o\4O\371\232\234\352m\255\313-"..., 4096) = 4096
+write(7, "\203\r\32\203&\205\373\204\7Ts\2117 ..R\245q\221\202\236\23B%\5u\2450X|c"..., 4096) = 4096
+write(7, "\343\23\235\207\357\272\vU9\32\243\5\255\35\321^Ggct\0\22!\232\230\206\204\340\270`AU"..., 4096) = 4096
+write(7, "\327\272k\273\203\301\241x\213]\257c\276\374\331\324\273O\217\327\317~v\327C\357=>^\273\347\241"..., 4096) = 4096
+write(7, "\3057=\344\235L\375\3623j\203^\313\202\201Uk\264,W\320\332\331\0371\344\34595\350\374\352"..., 4096) = 4096
+write(7, "\340,\327\302\334\203\322qZ\356A\f3|\234\207\263Hx\231\223\2\257\20^K\310\327ip17"..., 4096) = 4096
+write(7, "\375\1L,\4\10\246\26\346\347\3+\305\3630O\346\367+\357\225w\363\201\364\v\4Q\312\260p>"..., 4096) = 4096
+write(7, "\321\232\307\326{z\2264&3\23\227\206/q\2\332,x\361\336\343\233\6\16\365\26\206?\3300\342"..., 4096) = 4096
+mmap(NULL, 139264, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb0c4152000
+write(9, "*\343:\347\363\222\313SJx'\27\215\247FY\323\273\214Z\263\342\227\21\235\373\0o\246\377\254\36"..., 4096) = 4096
+write(9, "\320\333\302\v=\224\336\272\273\320C\241{\351\337\257\244\371\360\330\361\204\335\22H\34\217Fz$=#"..., 4096) = 4096
+write(9, "3e\367\262\357V\323\236X\2301\346hSUL&\v\207\246\340\36\26\203\177\336\3\f\354V\232\226"..., 4096) = 4096
+write(9, "6\341\25\225\274\212\212WlM\230\345\31o\213A\305$G\325\362\20\271\367\31}\331\231\233E\366="..., 4096) = 4096
+write(9, "\323\201)\320E\341\314d\36\303Le\2\277\3601\214\344\334\361\350\325\326\3640\360\223g\300|fs"..., 4096) = 4096
+write(9, "\302\322\307u}A\331\210\303\7\243\331\224`\215\225v\1&r\3370\260\243\304\222\242\255\2056._"..., 4096) = 4096
+write(9, "j\n\n209 0 obj\n1357\nendobj\n\n211 0 "..., 4096) = 4096
+write(9, "\341%\353\225\270\223\260'\324+\361\223\330+\361J\257$\216\263W\3425}\301q\364J|\276^I"..., 4096) = 4096
+write(9, "\303\366\1\334\231\234P\223#P\333<\343\264\225\334k\3109\354\3375\317\210\2B\310\211\234\266sz"..., 4096) = 4096
+write(9, "tc\\Ib?e\202\252L\243`0T\3755\23\314\200F\324c\312\6 at K\20p_\367R\31"..., 4096) = 4096
+write(9, "uTo\1\223Q\231\247j\30X\223F\222\204\337\274\373\275\31\326'`\351\374\206t\265\305Q\210\261"..., 4096) = 4096
+write(9, "e[C\363\32qdR\354\23\247&\372\6\6w\364Ml\23\307\206n~\276id\363\340D\337\324"..., 4096) = 4096
+write(9, "W\277\267\253w\363Pmf \351\27\2649\256\260\375\252f\274C\256t3HrIM\241F\32b"..., 4096) = 4096
+write(9, "\255\3\225\375EE\346\nKQsc\203;\3675\360\256g\324j\r\272\377vw\201;G\5,\224"..., 4096) = 4096
+write(9, "N\306\351,\312\236\327\251\213\346\2113C\332T4S\203\372b$}\242\343\365\304\312\361\36^\ta"..., 4096) = 4096
+write(9, "\256\233\234(\227\272;[\332\373\232\7:c\33\245\251yi\\Z\230\33\237\230\334;>w\2554\275"..., 4096) = 4096
+write(9, "sq\\e\325\357\355\354[\23\262\365X\373p\246\255\261wZ\35!\3372\354\340e\316\316\376el"..., 4096) = 4096
+write(9, "\266\356\260\3626+\230\325]v\215GC5\252\200\33\25H\237\336\321h6\253\343\3\"\247V\274&"..., 4096) = 4096
+read(8, "\264A\270\204\374\252\312\17_\10{f9T\351W\253\241\210/\r\3664\207\214\327\247~s\23\253\342"..., 4096) = 4096
+read(8, "/a\37\210>\360!\277Cps\216M\5*\201\17\361M|\214_\340q\332A\342\37ss\372\21"..., 4096) = 4096
+read(8, "\211\213\257_\314J+\307Ka\374\214:\244\201\375K\341|OFuz\240\232\351\270\365\205[V\227"..., 4096) = 4096
+read(8, "\3265%[\353\266vne\267\254u\271\23\303K\356\315\303Q\3670vl\1\271\331<\220\0351\17"..., 4096) = 4096
+read(8, "#\306\327ew\267\361\242,\27y\\x\t\351\205\264\244x\26\255\205\222\342B\352L\327Su\346,"..., 4096) = 4096
+read(8, "D\213\305\266\335v\330F\317\332\300m\v\330\216\332\270\230T\377Y,\26U\375\330\333\27\213\200$\251"..., 4096) = 4096
+read(8, "\3660/\177\221\31\7$\327\223\353\313\355\311\35\316\335\221{$We\317\275\234K\323\226\237\326\202G"..., 4096) = 4096
+read(8, "xX\317\2627+;M\375X\224\3765J=\21\256\r\3433\210|\357W\270]\250\244\256U\3328"..., 4096) = 4096
+read(8, "$\272\300\320b\216\26_\224\361\331_V\\\304\264<\250w\353\323\335zw\r\21ci\370\301\3300"..., 4096) = 4096
+read(8, "\302H\203\214\204\3210 l\220\334)\343M2\31\317\341\26(\332I\31\201\5^\325\320\370\221F\303"..., 4096) = 4096
+read(8, "SnZ.\323\340\35\361\222}\342\254H\312E\274/\t\317\332\360*}\227\236\34R\343\260zH\275"..., 4096) = 4096
+read(8, "00 600 600 600 600 600 600 600 6"..., 4096) = 4096
+read(8, "\312\355\315:\222E}\26599\26\347\"U\270\326\350\6\267{A\355}*\230Q\1Q\201J\265\272"..., 4096) = 4096
+read(8, "8\17\177\204m\367\220i\371\376^\327N\327\235.n\255\rZ\235\220CA\225`M\240\32'\323\311"..., 4096) = 4096
+read(8, "\30\357\33\214h\2356\220A\302v\301\21+\211D\320\347*\v\226\241p\24\321\274\35\243\27\36\231Z"..., 4096) = 4096
+read(8, "\247c>\355\n<e\306\277\253\265\\\326\274\0p\n\236\204\330\242F\226\305\26=E\236$\224{\330"..., 4096) = 4096
+read(8, "\264\254\274\242rb\244\252Z\2354yJMm\335\324h\375\264\206[\246\317\230\3318k\366\234\246\346"..., 4096) = 4096
+read(8, "*qm\246\306\0249\30nD1L~\221i!f&O\250\334\3653\335\204\306\3705\261B\361\371"..., 4096) = 4096
+read(8, "\3529\250\344 \333\10\rh>\302\254\213p\rn\3028\33\227`,R\274\231G\310,\3\33\223a"..., 4096) = 4096
+read(8, "P\36V\236RN)\\\267\2\271J\231\322\242\20'#\201?)\360\276\2O)?S\360\1\5\6"..., 4096) = 4096
+read(8, "\303e\226\215\271\215:9\255\360\312\353U\330\331\275\33\235\333\2569\304\27_\225xv\204\317\242)\4"..., 4096) = 4096
+read(8, "\2778\320q\177\320\327\326\256\311\231\315\263\345\34\207#_\256\366\266\310\236&m\256\\\356o\266\311\305"..., 4096) = 4096
+read(8, "\203\0258\262\263\202\365\356\360\320wt\5\305\32\f\210\3\2211\252=\235\343\305\22\3\3758Dc5"..., 4096) = 4096
+read(8, "\254\252n\3277\314-G^\333\275\357\255\7V\256\30\330\273R\327>\370\322\351\320\334\360\220\266\351\276"..., 4096) = 4096
+read(8, "oX\327\323\247\v\375VU1\375\324\255S\301[+V\337\366\340\201\351x\372\331\3576\337\273c\250"..., 4096) = 4096
+read(6, "\316\304WX\324\250\340\267\211\300\200\350A\366M@\255\330\354+\301j8F\244]\344\16Gjag"..., 4096) = 4096
+read(6, "G\217<w\3669\345\24\200\3769\235\336o8\0368N\217q\21\232\223H-\336\315xs\344\0>"..., 4096) = 4096
+read(6, "]\374\226\2576s8w\366\224\2\236\17\326\314\316\35\235@\353\262ut\36\227\306MG\331\264\206{"..., 4096) = 4096
+read(6, "\"\240$y\221\362\10vE@\214\300\317?\211|\35\301\335\24\300\35\210\274\34y3\302\323\344\351W"..., 4096) = 4096
+read(6, "\3467\327\16\236\333\35\232\26v\325\336yl\305m\303\375\223G\217\36\2331\270\2642\330\334\3270\265"..., 4096) = 4096
+read(6, "\3469\273\327\314M\33\235\217\233C\233\37{u\305\363\243_\34o\305oh]\24\234\333?\275\244\250"..., 4096) = 4096
+read(6, "A\337\3002\277\246)\3124\7\305\275\24S\266k\207p\360\305T\272\300\354\266.\260\347gL\f6"..., 4096) = 4096
+read(6, "\216\277\3025\364s\232'\353u\332\206\363\322\347s=~\251\257!\263\252\347C\273\274\356\346\307\363R"..., 4096) = 4096
+read(6, "\32\247\344\3620\317\214\37C9,#\273\237G\326\26xa\36\22D\203\214\230eD\220\241\214i\347"..., 4096) = 4096
+read(6, "%\222m\352\00147b\"\3768\332v\314Y\205=\7\357\317m*\317E\225`\357\233\375\207\321\343"..., 4096) = 4096
+read(6, "\374\340\343\317\36\376\333\373\277\230\234\371\315\7\37\355?\360\321\7\277\336\211c\373p\354\235\322\330+\304"..., 4096) = 4096
+read(6, "W\311:\371\36y\355\352x\201\207j(\2\31\371'\330F~L\336#o\220W\310\303\357\233W\20"..., 4096) = 4096
+read(6, "z\347\342\227N\356Y\253]\30s%\376J\373o\377\206Z\356C\271\\F;\253$~\362T \272"..., 4096) = 4096
+read(6, "\201\35\364\215\355>\312\306\215\233\2731_0\202^X\24\24\5S\320\273u\236Z\363\334\216\20\372\252"..., 4096) = 4096
+read(6, "^\22\341q\225F\303s\327\370\342\272A\347V\244\327W\27\210Bny\205e\16c(\201\tF\v"..., 4096) = 4096
+read(6, "\364\334:\26]\300\203ES\233\211\236t\210*\213(\2524\372d}\216\276\217-#\361{D\10\210"..., 4096) = 4096
+read(6, "\0\275!\351y?|I\251\364\200\37v\372a\243\37j\375P\344\207\17\375\360\242\37\36S*\210\345"..., 4096) = 4096
+read(6, "+\271\323\10\235\306\1#5\32u#\211b\354\275\205!\26\337\34R\2\234CW\313g\277\315\21_"..., 4096) = 4096
+read(6, "48>>\nstream\nx\234\325\274\7x\33\307\2650:3\273\213^\26\215\0\t\202"..., 4096) = 4096
+read(6, "G\36\31\34<>Z\261~\223\252\376\326U\345\313\217\276\17\263\337\7\374\264R\344c\237\220$\223Q"..., 4096) = 4096
+read(6, "*\225\354\10\r\335p2\213\270G)\306e6\323\355t\300\230\207\356\276\350\304\220\r/n\31P<"..., 4096) = 4096
+read(6, "\244\362\254\330\323\25\330\262<\277a\342\276\245\366\232\206\305\331\331~\343=I+\n<\3\231I\371&"..., 4096) = 4096
+read(6, "M>{\337;c\357\354z\207\351{k\354\255]o1\347\277\205\277\31N\267\217\237\303\216s\371\347"..., 4096) = 4096
+read(6, "0 0 595 842]/Annots[\n304 0 R 324"..., 4096) = 4096
+read(6, "0 R/MediaBox[0 0 595 842]/Group<"..., 4096) = 4096
+write(7, "w\274(Y\23\274Lce\262\225Y\35\214\345\321P\34\223\253\306\335\270\206\343M5\2475,\240\301"..., 4096) = 4096
+write(7, "\316\304WX\324\250\340\267\211\300\200\350A\366M@\255\330\354+\301j8F\244]\344\16Gjag"..., 4096) = 4096
+write(7, "G\217<w\3669\345\24\200\3769\235\336o8\0368N\217q\21\232\223H-\336\315xs\344\0>"..., 4096) = 4096
+write(7, "]\374\226\2576s8w\366\224\2\236\17\326\314\316\35\235@\353\262ut\36\227\306MG\331\264\206{"..., 4096) = 4096
+write(7, "\"\240$y\221\362\10vE@\214\300\317?\211|\35\301\335\24\300\35\210\274\34y3\302\323\344\351W"..., 4096) = 4096
+write(7, "\3467\327\16\236\333\35\232\26v\325\336yl\305m\303\375\223G\217\36\2331\270\2642\330\334\3270\265"..., 4096) = 4096
+write(7, "\3469\273\327\314M\33\235\217\233C\233\37{u\305\363\243_\34o\305oh]\24\234\333?\275\244\250"..., 4096) = 4096
+write(7, "A\337\3002\277\246)\3124\7\305\275\24S\266k\207p\360\305T\272\300\354\266.\260\347gL\f6"..., 4096) = 4096
+write(7, "\216\277\3025\364s\232'\353u\332\206\363\322\347s=~\251\257!\263\252\347C\273\274\356\346\307\363R"..., 4096) = 4096
+write(7, "\32\247\344\3620\317\214\37C9,#\273\237G\326\26xa\36\22D\203\214\230eD\220\241\214i\347"..., 4096) = 4096
+write(7, "%\222m\352\00147b\"\3768\332v\314Y\205=\7\357\317m*\317E\225`\357\233\375\207\321\343"..., 4096) = 4096
+write(7, "\374\340\343\317\36\376\333\373\277\230\234\371\315\7\37\355?\360\321\7\277\336\211c\373p\354\235\322\330+\304"..., 4096) = 4096
+write(7, "W\311:\371\36y\355\352x\201\207j(\2\31\371'\330F~L\336#o\220W\310\303\357\233W\20"..., 4096) = 4096
+write(7, "z\347\342\227N\356Y\253]\30s%\376J\373o\377\206Z\356C\271\\F;\253$~\362T \272"..., 4096) = 4096
+write(7, "\201\35\364\215\355>\312\306\215\233\2731_0\202^X\24\24\5S\320\273u\236Z\363\334\216\20\372\252"..., 4096) = 4096
+write(7, "^\22\341q\225F\303s\327\370\342\272A\347V\244\327W\27\210Bny\205e\16c(\201\tF\v"..., 4096) = 4096
+write(7, "\364\334:\26]\300\203ES\233\211\236t\210*\213(\2524\372d}\216\276\217-#\361{D\10\210"..., 4096) = 4096
+write(7, "\0\275!\351y?|I\251\364\200\37v\372a\243\37j\375P\344\207\17\375\360\242\37\36S*\210\345"..., 4096) = 4096
+write(7, "+\271\323\10\235\306\1#5\32u#\211b\354\275\205!\26\337\34R\2\234CW\313g\277\315\21_"..., 4096) = 4096
+write(7, "48>>\nstream\nx\234\325\274\7x\33\307\2650:3\273\213^\26\215\0\t\202"..., 4096) = 4096
+write(7, "G\36\31\34<>Z\261~\223\252\376\326U\345\313\217\276\17\263\337\7\374\264R\344c\237\220$\223Q"..., 4096) = 4096
+write(7, "*\225\354\10\r\335p2\213\270G)\306e6\323\355t\300\230\207\356\276\350\304\220\r/n\31P<"..., 4096) = 4096
+write(7, "\244\362\254\330\323\25\330\262<\277a\342\276\245\366\232\206\305\331\331~\343=I+\n<\3\231I\371&"..., 4096) = 4096
+write(7, "M>{\337;c\357\354z\207\351{k\354\255]o1\347\277\205\277\31N\267\217\237\303\216s\371\347"..., 4096) = 4096
+write(7, "0 0 595 842]/Annots[\n304 0 R 324"..., 4096) = 4096
+write(9, "M\347B\24\0278\264\10\31[\200\360<\25jU\240\342\2\24\354\24\f\24(Q\230\302\350\232f\214"..., 4096) = 4096
+write(9, "4<\355Q\253\305\353\17KG6\36\345i\327|\221G\361\250E\177\2744\371\r\305\242Y\203\367\212"..., 4096) = 4096
+write(9, "W\254:\314\264\244\35t\200\356\340f 7\252Rse\273b\307v\2737\301]w\206\351O2R"..., 4096) = 4096
+write(9, "C\v\257\304\306\224t\n\230\374\377\4\30:\222\256\340C\337U\"\202(\32*-_0\311\367\302U"..., 4096) = 4096
+write(9, "\226x\353\274K\275\33\275\333\275OzOz_\367~\344\375\302\233\360\6\273b/\33\261c\357\177\34"..., 4096) = 4096
+write(9, "\3704\315U\370\230f\214?\20\341i\21\366\211PNiq\201\10`\24a\377\n\261_\334%>+"..., 4096) = 4096
+write(9, "\2161\313\243\333\f\373\242\331D:e\334\367\257\227\6'\255\373.\221A\6r0J!g\310\30\337"..., 4096) = 4096
+write(9, "\32@\364\316\315\5\\\216\300\334\300\334\340\374\342\336\215.\207\3015wZ\243\231\233\252G\f\223\326n"..., 4096) = 4096
+write(9, "\246I\37\303?s~\373J\231\17\274\337\300D)\363\255$\211\n78\313BD\351\24E\204\203\37"..., 4096) = 4096
+write(9, "\27\4X\2\361\232\375p\3l\350\336;1\320\256'\240>\374\203\205;\245\27\267!\2\331\302\203\330"..., 4096) = 4096
+write(9, "A\367l \323\216\244\326\210[\324\272 at S \321~\245q\261\221\2666Bc at m\367\7[\2024"..., 4096) = 4096
+write(9, "/aP\22\355\25]|\22\265\206\26\10v\27\325\351\202\263\357\275'm\22H\347\306/d\7\321\336"..., 4096) = 4096
+write(9, "\312\231,\254%G\345?\352H,\335\272\26207\237\344+f*y\257\333]\317\17\305c|w4"..., 4096) = 4096
+write(9, "\225\345\343\312g\200\345\207\315\36s\246\307\354YD\335S\31\360\340\324fa\345\337\236X\304\277\311\214"..., 4096) = 4096
+write(9, "\323`\214\315\27\223\331b2\233M\354`\203d\252h\364\2332\235\6\31\302\5f\247H4\215\351&"..., 4096) = 4096
+write(9, "\6\266\205\332\2501.3\21\213\211\271B\315\234\206\253\247\200.\7H\32\7\316B7\372v\32\r5"..., 4096) = 4096
+write(9, "\375\223m--\326 at 1k\353\32l\353o>w\2\6\240\377sS\35\3127Q\246\337B{_\305"..., 4096) = 4096
+write(9, "\32]\326\266\354g\313\376\260\354?\227\t\17.\3\373\322\214\274Z\373\322T\367\377\323\262\331\3534\f"..., 4096) = 4096
+write(9, "jS}\276\224\313*.3f,*\261\367\17z\33\v\223\342\266\202\330\357\231g\250\245\200Q\35\350"..., 4096) = 4096
+write(9, "\267\271\300\261/\376x\354\30\253\216\307\264\243\235l\n\333\204\n\300:?&\324\354\310\237\315'\303~"..., 4096) = 4096
+write(9, "\331tj\223\4\316R\236\307\22\205J\262\356M|\207\241\250\322\237\22l^\22L\361W\372\f\321\333"..., 4096) = 4096
+write(9, "\271\320^\0003\242\20\313C\276\277y\347?\36\365\377\f\0221\230\257\25G\231\22\307\216\365\34\21\307"..., 4096) = 4096
+write(9, "aBox[0 0 595 842]/Group<</S/Tran"..., 4096) = 4096
+write(9, "[\n293 0 R 294 0 R 295 0 R ]\n/Gro"..., 4096) = 4096
+read(8, "\331\261c\7\260\4\26\331\201\3570\204a\314\311\252\16\347\303k\32\360\225[J\306\342\250ZX\315\204"..., 4096) = 4096
+read(8, "S/Transparency/CS/DeviceRGB/I tr"..., 4096) = 4096
+read(8, "32\n/Group<</S/Transparency/CS/De"..., 4096) = 4096
+read(8, "ources 3772 0 R/MediaBox[0 0 595"..., 4096) = 4096
+read(8, "ructElem\n/S/Standard\n/P 4 0 R\n/P"..., 4096) = 4096
+read(8, "lem\n/S/Standard\n/P 4 0 R\n/Pg 34 "..., 4096) = 4096
+read(8, "</Type/StructElem\n/S/Span\n/P 84 "..., 4096) = 4096
+read(8, "ctElem\n/S/TOCI\n/P 91 0 R\n/Pg 86 "..., 4096) = 4096
+read(8, "bj\n<</Type/StructElem\n/S/Content"..., 4096) = 4096
+read(8, "tructElem\n/S/Link\n/P 181 0 R\n/Pg"..., 4096) = 4096
+read(8, "\n<</Type/StructElem\n/S/Link\n/P 2"..., 4096) = 4096
+read(8, "dobj\n\n240 0 obj\n<</Type/StructEl"..., 4096) = 4096
+read(8, "ent/Block\n/StartIndent 56.6\n>>\ne"..., 4096) = 4096
+read(8, "ype/OBJR/Obj 3665 0 R>>\nendobj\n\n"..., 4096) = 4096
+read(8, "endobj\n\n4035 0 obj\n<</Type/OBJR/"..., 4096) = 4096
+read(8, " 367 0 R\n/Pg 272 0 R\n/A 4058 0 R"..., 4096) = 4096
+read(8, "ign/Justify\n>>\nendobj\n\n398 0 obj"..., 4096) = 4096
+read(8, "93 0 R\n/A 4098 0 R\n>>\nendobj\n\n40"..., 4096) = 4096
+read(8, " R\n/A 4121 0 R\n/K[5 ]\n>>\nendobj\n"..., 4096) = 4096
+read(8, "\n>>\nendobj\n\n4137 0 obj\n<</O/Layo"..., 4096) = 4096
+read(8, "nt/Block\n/SpaceBefore 11.3\n/Text"..., 4096) = 4096
+read(8, "xt#20body\n/P 4 0 R\n/Pg 493 0 R\n/"..., 4096) = 4096
+read(8, "/SpaceBefore 11.3\n/TextAlign/Jus"..., 4096) = 4096
+read(8, "ctElem\n/S/Text#20body\n/P 4 0 R\n/"..., 4096) = 4096
+read(8, "obj\n<</Type/StructElem\n/S/Span\n/"..., 4096) = 4096
+read(6, "95 842]/Group<</S/Transparency/C"..., 4096) = 4096
+read(6, "0 R/XYZ 103.4 255.9 0]\n/5F5FRefH"..., 4096) = 4096
+read(6, "st[178 0 R/XYZ 99.3 459.2 0]>>\ne"..., 4096) = 4096
+read(6, "293.1 538.8 306.9]/Dest[97 0 R/X"..., 4096) = 4096
+read(6, "<</Type/Action/S/URI/URI(http://"..., 4096) = 4096
+read(6, "ype/Catalog/Pages 327 0 R\n/Dests"..., 4096) = 4096
+read(6, "9380 00000 n \n0000215587 00000 n"..., 4096) = 4096
+read(6, " ]\n/DocChecksum /A9128BDB763FCCD"..., 4096) = 76
+read(6, "", 4096) = 0
+read(6, "", 4096) = 0
+write(7, "0 R/MediaBox[0 0 595 842]/Group<"..., 4096) = 4096
+write(7, "95 842]/Group<</S/Transparency/C"..., 4096) = 4096
+write(7, "0 R/XYZ 103.4 255.9 0]\n/5F5FRefH"..., 4096) = 4096
+write(7, "st[178 0 R/XYZ 99.3 459.2 0]>>\ne"..., 4096) = 4096
+write(7, "293.1 538.8 306.9]/Dest[97 0 R/X"..., 4096) = 4096
+write(7, "<</Type/Action/S/URI/URI(http://"..., 4096) = 4096
+write(7, "ype/Catalog/Pages 327 0 R\n/Dests"..., 4096) = 4096
+write(7, "9380 00000 n \n0000215587 00000 n"..., 4096) = 4096
+write(9, "0 595 842]/Group<</S/Transparenc"..., 4096) = 4096
+write(9, "0]\n/5F5FRefHeading5F5F154955F187"..., 4096) = 4096
+write(9, "ink/Border[0 0 0]/Rect[70.2 481."..., 4096) = 4096
+write(9, "/Type/Annot/Subtype/Link/Border["..., 4096) = 4096
+write(9, "419.2]/A<</Type/Action/S/URI/URI"..., 4096) = 4096
+write(9, "t[64 0 R/XYZ 73.7 96.6 0]>>\nendo"..., 4096) = 4096
+write(9, " n \n0000210521 00000 n \n00002124"..., 4096) = 4096
+read(8, "an\n/P 674 0 R\n/Pg 611 0 R\n/Lang("..., 4096) = 4096
+read(8, "t/Block\n/SpaceBefore 11.3\n/Start"..., 4096) = 4096
+read(8, "/Span\n/P 735 0 R\n/Pg 683 0 R\n/La"..., 4096) = 4096
+read(8, "\n>>\nendobj\n\n770 0 obj\n<</Type/St"..., 4096) = 4096
+read(8, "8 0 R ]\n>>\nendobj\n\n800 0 obj\n<</"..., 4096) = 4096
+read(8, "<</Type/StructElem\n/S/verbatim\n/"..., 4096) = 4096
+read(8, ")\n/K[18 ]\n>>\nendobj\n\n869 0 obj\n<"..., 4096) = 4096
+read(8, "m\n/P 4 0 R\n/Pg 889 0 R\n/A 4358 0"..., 4096) = 4096
+read(8, "batim\n/P 4 0 R\n/Pg 889 0 R\n/A 43"..., 4096) = 4096
+read(8, "72 0 R ]\n>>\nendobj\n\n970 0 obj\n<<"..., 4096) = 4096
+read(8, "obj\n\n1004 0 obj\n<</Type/StructEl"..., 4096) = 4096
+read(8, "19 0 obj\n<</O/Layout\n/Placement/"..., 4096) = 4096
+read(8, "t 36\n/TextAlign/Justify\n>>\nendob"..., 4096) = 4096
+read(8, "ype/StructElem\n/S/Span\n/P 1097 0"..., 4096) = 4096
+read(8, "\n/Placement/Block\n/TextAlign/Jus"..., 4096) = 4096
+read(8, "7 0 R\n>>\nendobj\n\n4478 0 obj\n<</O"..., 4096) = 4096
+read(8, "ctElem\n/S/Text#20body\n/P 4 0 R\n/"..., 4096) = 4096
+read(8, "86 0 R\n/K[1222 0 R ]\n>>\nendobj\n\n"..., 4096) = 4096
+read(8, "bj\n\n4521 0 obj\n<</O/Layout\n/Plac"..., 4096) = 4096
+read(8, "3 0 R\n/A 4536 0 R\n/K[21 1284 0 R"..., 4096) = 4096
+read(8, " 1311 0 R 1312 0 R 1313 0 R 1314"..., 4096) = 4096
+read(8, "\n>>\nendobj\n\n4570 0 obj\n<</O/Layo"..., 4096) = 4096
+read(8, "\n1383 0 obj\n<</Type/StructElem\n/"..., 4096) = 4096
+read(8, "Type/StructElem\n/S/Text#20body\n/"..., 4096) = 4096
+read(8, "e 11.3\n>>\nendobj\n\n1444 0 obj\n<</"..., 4096) = 4096
+read(6, "", 4096) = 0
+read(8, "/Layout\n/Placement/Block\n/SpaceB"..., 4096) = 4096
+read(8, "obj\n<</Type/StructElem\n/S/Span\n/"..., 4096) = 4096
+read(8, "1483 0 R\n/A 4649 0 R\n/K[38 ]\n>>\n"..., 4096) = 4096
+read(8, "tructElem\n/S/Span\n/P 1577 0 R\n/P"..., 4096) = 4096
+read(8, "\n1607 0 obj\n<</Type/StructElem\n/"..., 4096) = 4096
+read(8, "ore 11.3\n/TextAlign/Justify\n>>\ne"..., 4096) = 4096
+read(8, "673 0 obj\n<</Type/StructElem\n/S/"..., 4096) = 4096
+read(8, "0 R\n/Pg 1699 0 R\n/A 4723 0 R\n/K["..., 4096) = 4096
+read(8, "/S/Span\n/P 1736 0 R\n/Pg 1699 0 R"..., 4096) = 4096
+read(8, " 0 R ]\n>>\nendobj\n\n1769 0 obj\n<</"..., 4096) = 4096
+read(8, " R\n/K[4 ]\n>>\nendobj\n\n4772 0 obj\n"..., 4096) = 4096
+read(8, "tElem\n/S/LBody\n/P 1829 0 R\n/Pg 1"..., 4096) = 4096
+read(8, "0 R\n/K[1860 0 R ]\n>>\nendobj\n\n186"..., 4096) = 4096
+read(8, "R\n/Lang(zxx)\n/K[28 ]\n>>\nendobj\n\n"..., 4096) = 4096
+read(8, "lem\n/S/Span\n/P 1931 0 R\n/Pg 1846"..., 4096) = 4096
+read(8, "stify\n>>\nendobj\n\n1964 0 obj\n<</T"..., 4096) = 4096
+read(8, "R 1980 0 R 1983 0 R 1986 0 R 198"..., 4096) = 4096
+read(8, "/Block\n>>\nendobj\n\n2020 0 obj\n<</"..., 4096) = 4096
+read(8, "uctElem\n/S/Span\n/P 2055 0 R\n/Pg "..., 4096) = 4096
+read(8, "\n/S/verbatim\n/P 4 0 R\n/Pg 2078 0"..., 4096) = 4096
+read(8, "</O/Layout\n/Placement/Block\n/Spa"..., 4096) = 4096
+read(8, "Align/Justify\n>>\nendobj\n\n2145 0 "..., 4096) = 4096
+read(8, "ent/Block\n/SpaceBefore 11.3\n>>\ne"..., 4096) = 4096
+read(8, " 0 R\n/A 4972 0 R\n/K[29 2207 0 R "..., 4096) = 4096
+read(8, "Elem\n/S/verbatim\n/P 4 0 R\n/Pg 22"..., 4096) = 4096
+read(8, "\n\n2274 0 obj\n<</Type/StructElem\n"..., 4096) = 4096
+read(8, "Pg 2231 0 R\n/A 5017 0 R\n/K[45 23"..., 4096) = 4096
+read(8, "lem\n/S/LBody\n/P 2334 0 R\n/Pg 232"..., 4096) = 4096
+read(8, "/K[2371 0 R ]\n>>\nendobj\n\n2377 0 "..., 4096) = 4096
+read(8, "/A 5060 0 R\n/K[37 ]\n>>\nendobj\n\n2"..., 4096) = 4096
+read(8, "endobj\n\n2442 0 obj\n<</Type/Struc"..., 4096) = 4096
+read(8, " 0 R ]\n>>\nendobj\n\n2468 0 obj\n<</"..., 4096) = 4096
+read(8, "n/Justify\n>>\nendobj\n\n2504 0 obj\n"..., 4096) = 4096
+read(8, "gn/Justify\n>>\nendobj\n\n2527 0 obj"..., 4096) = 4096
+read(8, "ify\n>>\nendobj\n\n2553 0 obj\n<</Typ"..., 4096) = 4096
+read(8, "0body\n/P 2584 0 R\n/Pg 2565 0 R\n/"..., 4096) = 4096
+read(8, "Indent 36\n/TextAlign/Justify\n>>\n"..., 4096) = 4096
+read(8, "obj\n\n2648 0 obj\n<</Type/StructEl"..., 4096) = 4096
+read(8, "t/Block\n/SpaceBefore 11.3\n/TextA"..., 4096) = 4096
+read(8, "\n>>\nendobj\n\n5233 0 obj\n<</O/Layo"..., 4096) = 4096
+read(8, ">>\nendobj\n\n2741 0 obj\n<</Type/St"..., 4096) = 4096
+read(8, "j\n<</Type/StructElem\n/S/Text#20b"..., 4096) = 4096
+read(8, "/verbatim\n/P 4 0 R\n/Pg 2796 0 R\n"..., 4096) = 4096
+read(8, "R\n/A 5297 0 R\n>>\nendobj\n\n5298 0 "..., 4096) = 4096
+read(8, " 2830 0 R\n/A 5313 0 R\n/K[2865 0 "..., 4096) = 4096
+read(8, "g 2830 0 R\n/A 5325 0 R\n/K[24 ]\n>"..., 4096) = 4096
+read(8, "\n/Pg 2897 0 R\n/Lang(zxx)\n/K[16 ]"..., 4096) = 4096
+read(8, "\nendobj\n\n2965 0 obj\n<</Type/Stru"..., 4096) = 4096
+read(8, "endobj\n\n5374 0 obj\n<</O/Layout\n/"..., 4096) = 4096
+read(8, "lem\n/S/Text#20body\n/P 4 0 R\n/Pg "..., 4096) = 4096
+read(8, "R\n/Pg 3024 0 R\n/A 5414 0 R\n/K[30"..., 4096) = 4096
+read(8, "\nendobj\n\n5433 0 obj\n<</O/Layout\n"..., 4096) = 4096
+read(8, "\n\n5454 0 obj\n<</O/Layout\n/Placem"..., 4096) = 4096
+read(8, " obj\n<</O/Layout\n/Placement/Bloc"..., 4096) = 4096
+read(8, " 5485 0 R\n/K[3176 0 R 3177 0 R ]"..., 4096) = 4096
+read(8, "21 0 R\n/A 5499 0 R\n/K[3209 0 R ]"..., 4096) = 4096
+read(8, "<</O/Layout\n/Placement/Block\n/Te"..., 4096) = 4096
+read(8, "ify\n>>\nendobj\n\n3268 0 obj\n<</Typ"..., 4096) = 4096
+read(8, "ructElem\n/S/Text#20body\n/P 4 0 R"..., 4096) = 4096
+read(8, "Justify\n>>\nendobj\n\n3332 0 obj\n<<"..., 4096) = 4096
+read(8, "t/Block\n/SpaceBefore 11.3\n>>\nend"..., 4096) = 4096
+read(8, "/Text#20body\n/P 4 0 R\n/Pg 3388 0"..., 4096) = 4096
+read(8, "33 0 obj\n<</O/Layout\n/Placement/"..., 4096) = 4096
+read(8, "\n\n3453 0 obj\n<</Type/StructElem\n"..., 4096) = 4096
+read(8, "<</O/Layout\n/Placement/Block\n/Sp"..., 4096) = 4096
+read(8, "pe/StructElem\n/S/Text#20body\n/P "..., 4096) = 4096
+read(8, "ut\n/Placement/Block\n/TextAlign/J"..., 4096) = 4096
+read(8, "<</Type/StructElem\n/S/Span\n/P 35"..., 4096) = 4096
+read(8, "ck\n/SpaceBefore 11.3\n/TextAlign/"..., 4096) = 4096
+read(8, "R 1078 0 R 1081 0 R 1082 0 R 108"..., 4096) = 4096
+read(8, " 2228 0 R 2230 0 R 2234 0 R 2236"..., 4096) = 4096
+read(8, "3220 0 R 3221 0 R 3222 0 R 3223 "..., 4096) = 4096
+read(8, " 434 0 R 433 0 R 439 0 R 447 0 R"..., 4096) = 4096
+read(8, " 0 R 1403 0 R 1405 0 R\n1406 0 R "..., 4096) = 4096
+read(8, "47 0 R 2149 0 R 2151 0 R 2153 0 "..., 4096) = 4096
+read(8, " R 2965 0 R 2967 0 R 2969 0 R\n29"..., 4096) = 4096
+read(8, "76 314 0 R\n177 317 0 R\n178 318 0"..., 4096) = 4096
+read(8, " obj\n<</Type/Annot/Subtype/Link/"..., 4096) = 4096
+read(8, "ink/Border[0 0 0]/Rect[56 715.9 "..., 4096) = 4096
+read(8, "8 0 obj\n<</Type/Annot/Subtype/Li"..., 4096) = 4096
+read(8, "ndobj\n\n3715 0 obj\n<</Type/Annot/"..., 4096) = 4096
+read(8, "0441541 00000 n \n0000441675 0000"..., 4096) = 4096
+read(8, "\n0000469005 00000 n \n0000468893 "..., 4096) = 4096
+read(8, "0 n \n0000496829 00000 n \n0000497"..., 4096) = 4096
+read(8, "00000 n \n0000522719 00000 n \n000"..., 4096) = 4096
+read(8, "766 00000 n \n0000548419 00000 n "..., 4096) = 4096
+read(8, "0574989 00000 n \n0000574733 0000"..., 4096) = 4096
+read(8, "\n0000601375 00000 n \n0000601467 "..., 4096) = 4096
+read(8, "0 n \n0000627289 00000 n \n0000627"..., 4096) = 4096
+read(8, "00000 n \n0000653206 00000 n \n000"..., 4096) = 4096
+read(8, "783 00000 n \n0000679694 00000 n "..., 4096) = 4096
+read(8, "0706504 00000 n \n0000706341 0000"..., 4096) = 4096
+read(8, "\n0000732171 00000 n \n0000732561 "..., 4096) = 4096
+read(8, "0 n \n0000760566 00000 n \n0000760"..., 4096) = 4096
+read(8, "00000 n \n0000787452 00000 n \n000"..., 4096) = 4096
+read(8, "753 00000 n \n0000813918 00000 n "..., 4096) = 4096
+read(8, "0840635 00000 n \n0000840820 0000"..., 4096) = 4096
+read(8, "\n0000867968 00000 n \n0000868263 "..., 4096) = 4096
+read(8, "0 n \n0000929507 00000 n \n0000929"..., 4096) = 4096
+read(8, "00000 n \n0000432015 00000 n \n000"..., 4096) = 4096
+read(8, "222 00000 n \n0000467486 00000 n "..., 4096) = 4096
+read(8, "0512314 00000 n \n0000512564 0000"..., 4096) = 4096
+read(8, "\n0000565053 00000 n \n0000565326 "..., 4096) = 4096
+read(8, "0 n \n0000623533 00000 n \n0000623"..., 4096) = 4096
+read(8, "00000 n \n0000682257 00000 n \n000"..., 4096) = 4096
+read(8, "308 00000 n \n0000733768 00000 n "..., 4096) = 4096
+read(8, "0782730 00000 n \n0000782993 0000"..., 4096) = 4096
+read(8, "\n0000830956 00000 n \n0000831219 "..., 4096) = 4096
+read(8, "0 n \n0000876188 00000 n \n0000876"..., 4096) = 1946
+read(8, "", 4096) = 0
+read(8, "", 4096) = 0
+close(8) = 0
+write(7, " ]\n/DocChecksum /A9128BDB763FCCD"..., 76) = 76
+close(7) = 0
+close(6) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\241\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741825, len=1}) = 0
+lseek(3, 141312, SEEK_SET) = 141312
+read(3, "\2\0\0\0\n\1\322\0\0\0\0b\2y\2A\2\t\2\260\2\350\3X\1\322\3 \3\220\3\310"..., 1024) = 1024
+lseek(3, 103424, SEEK_SET) = 103424
+read(3, "\n\0\0\0\22\0]\0\0\371\1-\1a\1\224\1\310\1\373\2/\0]\2c\2\227\2\313\2\377"..., 1024) = 1024
+rename("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/tmp/svn-2LgtUH", "/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/pristine/e9/e98ce4d859b08fcc744bd1f50aef6dd6f418493f.svn-base") = 0
+stat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/pristine/e9/e98ce4d859b08fcc744bd1f50aef6dd6f418493f.svn-base", {st_mode=S_IFREG|0664, st_size=438348, ...}) = 0
+lseek(3, 114688, SEEK_SET) = 114688
+read(3, "\n\0\0\0\26\0>\0\0>\0i\0\225\0\301\0\355\1\31\1D\1o\1\233\1\307\1\363\2\37"..., 1024) = 1024
+stat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db", {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+stat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db", {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+open("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", O_RDWR|O_CREAT|O_CLOEXEC, 0644) = 6
+fstat(6, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
+geteuid() = 530
+fstat(6, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
+lseek(6, 0, SEEK_SET) = 0
+write(6, "\331\325\5\371 \241c\327\377\377\377\377\267[\205\232\0\0\0\241\0\0\2\0\0\0\4\0\0\0\0\0"..., 512) = 512
+lseek(6, 512, SEEK_SET) = 512
+write(6, "\0\0\0006", 4) = 4
+lseek(6, 516, SEEK_SET) = 516
+write(6, "\2\1\370\0\21\0L\3\0\0\0\215\2S\0\253\0|\1\311\1\231\3p\1j\3\320\0\333\3A"..., 1024) = 1024
+lseek(6, 1540, SEEK_SET) = 1540
+write(6, "\267[\206g", 4) = 4
+lseek(3, 115712, SEEK_SET) = 115712
+read(3, "\n\0\0\0\25\0l\0\0l\0\230\0\303\0\357\1\33\1G\1s\1\237\1\312\1\366\2!\2M"..., 1024) = 1024
+lseek(3, 113664, SEEK_SET) = 113664
+read(3, "\n\0\0\0\26\0D\0\0D\0p\0\234\0\310\0\363\1\37\1J\1v\1\241\1\314\1\370\2$"..., 1024) = 1024
+lseek(6, 1544, SEEK_SET) = 1544
+write(6, "\0\0\0p", 4) = 4
+lseek(6, 1548, SEEK_SET) = 1548
+write(6, "\n\0\0\0\26\0D\0\0D\0p\0\234\0\310\0\363\1\37\1J\1v\1\241\1\314\1\370\2$"..., 1024) = 1024
+lseek(6, 2572, SEEK_SET) = 2572
+write(6, "\267[\206\315", 4) = 4
+lseek(6, 2576, SEEK_SET) = 2576
+write(6, "\0\0\0q", 4) = 4
+lseek(6, 2580, SEEK_SET) = 2580
+write(6, "\n\0\0\0\26\0>\0\0>\0i\0\225\0\301\0\355\1\31\1D\1o\1\233\1\307\1\363\2\37"..., 1024) = 1024
+lseek(6, 3604, SEEK_SET) = 3604
+write(6, "\267[\206\250", 4) = 4
+lseek(6, 3608, SEEK_SET) = 3608
+write(6, "\0\0\0r", 4) = 4
+lseek(6, 3612, SEEK_SET) = 3612
+write(6, "\n\0\0\0\25\0l\0\0l\0\230\0\303\0\357\1\33\1G\1s\1\237\1\312\1\366\2!\2M"..., 1024) = 1024
+lseek(6, 4636, SEEK_SET) = 4636
+write(6, "\267[\206\203", 4) = 4
+lseek(6, 4640, SEEK_SET) = 4640
+write(6, "\0\0\0\213", 4) = 4
+lseek(6, 4644, SEEK_SET) = 4644
+write(6, "\2\0\0\0\n\1\322\0\0\0\0b\2y\2A\2\t\2\260\2\350\3X\1\322\3 \3\220\3\310"..., 1024) = 1024
+lseek(6, 5668, SEEK_SET) = 5668
+write(6, "\267[\206\2", 4) = 4
+lseek(3, 136192, SEEK_SET) = 136192
+read(3, "\n\0\0\0\f\1\222\0\2.\2b\1\372\2\226\2\311\1\222\1\306\2\375\0031\3e\3\231\3\315"..., 1024) = 1024
+lseek(3, 160768, SEEK_SET) = 160768
+read(3, "\n\0\0\0\n\1\374\0\0020\2c\2\226\2\312\2\375\1\374\0030\3d\3\230\3\314\0\0\0\0"..., 1024) = 1024
+lseek(6, 5672, SEEK_SET) = 5672
+write(6, "\0\0\0\236", 4) = 4
+lseek(6, 5676, SEEK_SET) = 5676
+write(6, "\n\0\0\0\n\1\374\0\0020\2c\2\226\2\312\2\375\1\374\0030\3d\3\230\3\314\0\0\0\0"..., 1024) = 1024
+lseek(6, 6700, SEEK_SET) = 6700
+write(6, "\267[\206\1", 4) = 4
+lseek(6, 6704, SEEK_SET) = 6704
+write(6, "\0\0\0f", 4) = 4
+lseek(6, 6708, SEEK_SET) = 6708
+write(6, "\n\0\0\0\22\0]\0\0\371\1-\1a\1\224\1\310\1\373\2/\0]\2c\2\227\2\313\2\377"..., 1024) = 1024
+lseek(6, 7732, SEEK_SET) = 7732
+write(6, "\267[\207\5", 4) = 4
+lseek(6, 7736, SEEK_SET) = 7736
+write(6, "\0\0\0\206", 4) = 4
+lseek(6, 7740, SEEK_SET) = 7740
+write(6, "\n\0\0\0\f\1\222\0\2.\2b\1\372\2\226\2\311\1\222\1\306\2\375\0031\3e\3\231\3\315"..., 1024) = 1024
+lseek(6, 8764, SEEK_SET) = 8764
+write(6, "\267[\206\230", 4) = 4
+lseek(6, 8768, SEEK_SET) = 8768
+write(6, "\0\0\0\240", 4) = 4
+lseek(6, 8772, SEEK_SET) = 8772
+write(6, "\r\0\0\0\4\2\202\0\3\241\3B\2\342\2\202\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(6, 9796, SEEK_SET) = 9796
+write(6, "\267[\205\316", 4) = 4
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+lseek(6, 9800, SEEK_SET) = 9800
+write(6, "\0\0\0\1", 4) = 4
+lseek(6, 9804, SEEK_SET) = 9804
+write(6, "SQLite format 3\0\4\0\1\1\0@ \0\0\5\241\0\0\0\241"..., 1024) = 1024
+lseek(6, 10828, SEEK_SET) = 10828
+write(6, "\267[\205\232", 4) = 4
+lseek(3, 0, SEEK_SET) = 0
+write(3, "SQLite format 3\0\4\0\1\1\0@ \0\0\5\242\0\0\0\241"..., 1024) = 1024
+lseek(3, 54272, SEEK_SET) = 54272
+write(3, "\2\1\370\0\21\0L\3\0\0\0\215\2S\0\253\0|\1\311\1\231\3p\1j\3\320\0\333\3A"..., 1024) = 1024
+lseek(3, 103424, SEEK_SET) = 103424
+write(3, "\n\0\0\0\20\0\306\0\0\306\0\371\1,\1`\1\223\1\306\1\372\2.\2b\2\226\2\312\2\376"..., 1024) = 1024
+lseek(3, 113664, SEEK_SET) = 113664
+write(3, "\n\0\0\0\26\0D\0\0D\0p\0\234\0\310\0\363\1\37\1J\1v\1\241\1\314\1\370\2$"..., 1024) = 1024
+lseek(3, 114688, SEEK_SET) = 114688
+write(3, "\n\0\0\0\26\0>\0\0>\0i\0\225\0\301\0\355\1\31\1D\1o\1\233\1\307\1\363\2\37"..., 1024) = 1024
+lseek(3, 115712, SEEK_SET) = 115712
+write(3, "\n\0\0\0\26\0@\0\0@\0l\0\230\0\303\0\357\1\33\1G\1s\1\237\1\312\1\366\2!"..., 1024) = 1024
+lseek(3, 136192, SEEK_SET) = 136192
+write(3, "\n\0\0\0\16\1+\0\1+\1_\1\223\1\307\1\373\2/\2b\2\225\2\311\2\374\0030\3d"..., 1024) = 1024
+lseek(3, 141312, SEEK_SET) = 141312
+write(3, "\2\0\0\0\n\1\322\0\0\0\0b\2y\2A\2\t\2\260\2\350\3X\1\322\3 \3\220\3\310"..., 1024) = 1024
+lseek(3, 160768, SEEK_SET) = 160768
+write(3, "\n\0\0\0\v\1\306\0\1\306\1\372\2.\2a\2\225\2\311\2\375\0031\3e\3\231\3\315\0\0"..., 1024) = 1024
+lseek(3, 162816, SEEK_SET) = 162816
+write(3, "\r\0\0\0\5\2\"\0\3\241\3B\2\342\2\202\2\"\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+close(6) = 0
+unlink("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal") = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=2}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLOUT, {u32=3303153608, u64=140397194125256}}) = 0
+epoll_wait(4, {{EPOLLOUT, {u32=3303153608, u64=140397194125256}}}, 16, 500) = 1
+write(9, "0000 n \n0000430066 00000 n \n0000"..., 246) = 246
+lseek(9, 0, SEEK_SET) = 0
+stat("/tmp/svn-S2P1pi", {st_mode=S_IFREG|0600, st_size=315638, ...}) = 0
+mmap(NULL, 315638, PROT_READ, MAP_SHARED, 9, 0) = 0x7fb0c4104000
+writev(5, [{"PUT /svn/aprx/!svn/wrk/9ce1e70b-"..., 96}, {"Host", 4}, {": ", 2}, {"repo.ham.fi", 11}, {"\r\n", 2}, {"Authorization", 13}, {": ", 2}, {"Basic b2gybXFrOnJpZnJhZjIy", 26}, {"\r\n", 2}, {"User-Agent", 10}, {": ", 2}, {"SVN/1.8.8 (x86_64-redhat-linux-g"..., 46}, {"\r\n", 2}, {"Content-Type", 12}, {": ", 2}, {"application/vnd.svn-svndiff", 27}, {"\r\n", 2}, {"Accept-Encoding", 15}, {": ", 2}, {"gzip", 4}, {"\r\n", 2}, {"DAV", 3}, {": ", 2}, {"http://subversion.tigris.org/xml"... [...]
+writev(5, [{"\242\5\36\341\313\222\"\237\0053\204\364\f\243\265\0\271\22\2304D\376dVK\206\261\n\370\275+v"..., 94736}, {"\r\n", 2}, {"0\r\n\r\n", 5}], 3) = -1 EAGAIN (Resource temporarily unavailable)
+epoll_wait(4, {{EPOLLOUT, {u32=3303153608, u64=140397194125256}}}, 16, 500) = 1
+writev(5, [{"\242\5\36\341\313\222\"\237\0053\204\364\f\243\265\0\271\22\2304D\376dVK\206\261\n\370\275+v"..., 94736}, {"\r\n", 2}, {"0\r\n\r\n", 5}], 3) = 86880
+writev(5, [{"00000000 65535 f \n0000399797 000"..., 7856}, {"\r\n", 2}, {"0\r\n\r\n", 5}], 3) = -1 EAGAIN (Resource temporarily unavailable)
+epoll_wait(4, {{EPOLLOUT, {u32=3303153608, u64=140397194125256}}}, 16, 500) = 1
+writev(5, [{"00000000 65535 f \n0000399797 000"..., 7856}, {"\r\n", 2}, {"0\r\n\r\n", 5}], 3) = 7863
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN, {u32=3303153656, u64=140397194125304}}) = 0
+epoll_wait(4, {{EPOLLIN, {u32=3303153656, u64=140397194125304}}}, 16, 500) = 1
+read(5, "HTTP/1.1 204 No Content\r\nDate: M"..., 8000) = 106
+munmap(0x7fb0c4104000, 315638) = 0
+close(9) = 0
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLOUT, {u32=3303153608, u64=140397194125256}}) = 0
+epoll_wait(4, {{EPOLLOUT, {u32=3303153608, u64=140397194125256}}}, 16, 500) = 1
+writev(5, [{"MERGE /svn/aprx/trunk/doc HTTP/1"..., 36}, {"Host", 4}, {": ", 2}, {"repo.ham.fi", 11}, {"\r\n", 2}, {"Authorization", 13}, {": ", 2}, {"Basic b2gybXFrOnJpZnJhZjIy", 26}, {"\r\n", 2}, {"User-Agent", 10}, {": ", 2}, {"SVN/1.8.8 (x86_64-redhat-linux-g"..., 46}, {"\r\n", 2}, {"Accept-Encoding", 15}, {": ", 2}, {"gzip", 4}, {"\r\n", 2}, {"DAV", 3}, {": ", 2}, {"http://subversion.tigris.org/xml"..., 48}, {"\r\n", 2}, {"DAV", 3}, {": ", 2}, {"http://subversion.tigris.org/xml"..., [...]
+writev(5, [{"2b\r\n", 4}, {"</", 2}, {"D:creator-displayname", 21}, {">", 1}, {"</", 2}, {"D:prop", 6}, {">", 1}, {"</", 2}, {"D:merge", 7}, {">", 1}, {"\r\n", 2}, {"0\r\n\r\n", 5}], 12) = 54
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN, {u32=3303153656, u64=140397194125304}}) = 0
+epoll_wait(4, {{EPOLLIN, {u32=3303153656, u64=140397194125304}}}, 16, 500) = 1
+read(5, "HTTP/1.1 200 OK\r\nDate: Mon, 24 M"..., 8000) = 542
+brk(0) = 0x7fb0c4e6c000
+brk(0x7fb0c4e8d000) = 0x7fb0c4e8d000
+brk(0) = 0x7fb0c4e8d000
+brk(0) = 0x7fb0c4e8d000
+brk(0x7fb0c4e85000) = 0x7fb0c4e85000
+brk(0) = 0x7fb0c4e85000
+write(1, "\nCommitted revision 590.\n", 25) = 25
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLOUT, {u32=3303153608, u64=140397194125256}}) = 0
+epoll_wait(4, {{EPOLLOUT, {u32=3303153608, u64=140397194125256}}}, 16, 500) = 1
+writev(5, [{"DELETE /svn/aprx/!svn/act/9ce1e7"..., 73}, {"Host", 4}, {": ", 2}, {"repo.ham.fi", 11}, {"\r\n", 2}, {"Authorization", 13}, {": ", 2}, {"Basic b2gybXFrOnJpZnJhZjIy", 26}, {"\r\n", 2}, {"User-Agent", 10}, {": ", 2}, {"SVN/1.8.8 (x86_64-redhat-linux-g"..., 46}, {"\r\n", 2}, {"Accept-Encoding", 15}, {": ", 2}, {"gzip", 4}, {"\r\n", 2}, {"DAV", 3}, {": ", 2}, {"http://subversion.tigris.org/xml"..., 48}, {"\r\n", 2}, {"DAV", 3}, {": ", 2}, {"http://subversion.tigris.org/xml"..., [...]
+epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = 0
+epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN, {u32=3303153656, u64=140397194125304}}) = 0
+epoll_wait(4, {{EPOLLIN, {u32=3303153656, u64=140397194125304}}}, 16, 500) = 1
+read(5, "HTTP/1.1 204 No Content\r\nDate: M"..., 8000) = 106
+unlink("/tmp/svn-S2P1pi") = 0
+unlink("/tmp/svn-4fJ8w7") = 0
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/aprx-manual.odt", {st_mode=S_IFREG|0664, st_size=197896, ...}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\242\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\242\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741825, len=1}) = 0
+stat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db", {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+stat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db", {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+open("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", O_RDWR|O_CREAT|O_CLOEXEC, 0644) = 6
+fstat(6, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
+geteuid() = 530
+fstat(6, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
+lseek(6, 0, SEEK_SET) = 0
+write(6, "\331\325\5\371 \241c\327\377\377\377\377\3200\257Y\0\0\0\241\0\0\2\0\0\0\4\0\0\0\0\0"..., 512) = 512
+lseek(6, 512, SEEK_SET) = 512
+write(6, "\0\0\0t", 4) = 4
+lseek(6, 516, SEEK_SET) = 516
+write(6, "\n\0\0\0\21\1\265\0\1\265\1\316\1\360\2\n\2-\2L\2k\2\234\2\315\2\357\3\16\0030"..., 1024) = 1024
+lseek(6, 1540, SEEK_SET) = 1540
+write(6, "\3200\257\323", 4) = 4
+lseek(3, 101376, SEEK_SET) = 101376
+read(3, "\n\0035\0`\1\30\0\3\371\3\362\3\353\3\344\3\335\3\326\3\317\3\310\3\301\3\272\3\263\3\254"..., 1024) = 1024
+lseek(6, 1544, SEEK_SET) = 1544
+write(6, "\0\0\0d", 4) = 4
+lseek(6, 1548, SEEK_SET) = 1548
+write(6, "\n\0035\0`\1\30\0\3\371\3\362\3\353\3\344\3\335\3\326\3\317\3\310\3\301\3\272\3\263\3\254"..., 1024) = 1024
+lseek(6, 2572, SEEK_SET) = 2572
+write(6, "\3200\257p", 4) = 4
+lseek(6, 2576, SEEK_SET) = 2576
+write(6, "\0\0\0005", 4) = 4
+lseek(6, 2580, SEEK_SET) = 2580
+write(6, "\n\3\226\0\35\0012\2\1d\1|\1\233\0012\1\304\1\333\1\356\2\0\2\33\1\260\2B\2M"..., 1024) = 1024
+lseek(6, 3604, SEEK_SET) = 3604
+write(6, "\3200\260:", 4) = 4
+lseek(6, 3608, SEEK_SET) = 3608
+write(6, "\0\0\0h", 4) = 4
+lseek(6, 3612, SEEK_SET) = 3612
+write(6, "\r\0\0\0\3\0\311\0\0\311\1\341\2\350\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(6, 4636, SEEK_SET) = 4636
+write(6, "\3200\2602", 4) = 4
+lseek(6, 4640, SEEK_SET) = 4640
+write(6, "\0\0\0\224", 4) = 4
+lseek(6, 4644, SEEK_SET) = 4644
+write(6, "\r\0\0\0\n\0F\0\3\241\3A\2\341\2\202\2\"\1\303\1d\1\5\0\245\0F\0\0\0\0"..., 1024) = 1024
+lseek(6, 5668, SEEK_SET) = 5668
+write(6, "\3200\260h", 4) = 4
+lseek(6, 5672, SEEK_SET) = 5672
+write(6, "\0\0\0\241", 4) = 4
+lseek(6, 5676, SEEK_SET) = 5676
+write(6, "\r\3(\0\2\1\301\0\1\301\2\244\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(6, 6700, SEEK_SET) = 6700
+write(6, "\3200\257\313", 4) = 4
+lseek(6, 6704, SEEK_SET) = 6704
+write(6, "\0\0\0\240", 4) = 4
+lseek(6, 6708, SEEK_SET) = 6708
+write(6, "\r\0\0\0\5\2\"\0\3\241\3B\2\342\2\202\2\"\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(6, 7732, SEEK_SET) = 7732
+write(6, "\3200\257\304", 4) = 4
+lseek(3, 3072, SEEK_SET) = 3072
+read(3, "\r\0\0\0\3\3\325\0\3\361\3\346\3\325\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(6, 7736, SEEK_SET) = 7736
+write(6, "\0\0\0\25", 4) = 4
+lseek(6, 7740, SEEK_SET) = 7740
+write(6, "\r\0\0\0\0\4\0\0\2\230\2\230\2\230\2\230\2\230\2\230\2\230\2\230\2\230\1\232\1\232\1\232"..., 1024) = 1024
+lseek(6, 8764, SEEK_SET) = 8764
+write(6, "\3200\257[", 4) = 4
+lseek(6, 8768, SEEK_SET) = 8768
+write(6, "\0\0\0\4", 4) = 4
+lseek(6, 8772, SEEK_SET) = 8772
+write(6, "\r\0\0\0\3\3\325\0\3\361\3\346\3\325\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(6, 9796, SEEK_SET) = 9796
+write(6, "\3200\257Y", 4) = 4
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+lseek(6, 9800, SEEK_SET) = 9800
+write(6, "\0\0\0\1", 4) = 4
+lseek(6, 9804, SEEK_SET) = 9804
+write(6, "SQLite format 3\0\4\0\1\1\0@ \0\0\5\242\0\0\0\241"..., 1024) = 1024
+lseek(6, 10828, SEEK_SET) = 10828
+write(6, "\3200\257Y", 4) = 4
+lseek(3, 0, SEEK_SET) = 0
+write(3, "SQLite format 3\0\4\0\1\1\0@ \0\0\5\243\0\0\0\241"..., 1024) = 1024
+lseek(3, 3072, SEEK_SET) = 3072
+write(3, "\r\0\0\0\3\3\325\0\3\361\3\346\3\325\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(3, 20480, SEEK_SET) = 20480
+write(3, "\r\0\0\0\1\3\331\0\3\331\2\230\2\230\2\230\2\230\2\230\2\230\2\230\2\230\1\232\1\232\1\232"..., 1024) = 1024
+lseek(3, 53248, SEEK_SET) = 53248
+write(3, "\n\3\226\0\35\0012\2\1d\1|\1\233\0012\1\304\1\333\1\356\2\0\2\33\1\260\2B\2M"..., 1024) = 1024
+lseek(3, 101376, SEEK_SET) = 101376
+write(3, "\n\0035\0`\1\30\0\3\371\3\362\3\353\3\344\3\335\3\326\3\317\3\310\3\301\3\272\3\263\3\254"..., 1024) = 1024
+lseek(3, 105472, SEEK_SET) = 105472
+write(3, "\r\2\350\0\2\0\311\0\0\311\1\341\2\350\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(3, 117760, SEEK_SET) = 117760
+write(3, "\n\0\0\0\21\1\265\0\1\265\1\316\1\360\2\n\2-\2L\2k\2\234\2\315\2\357\3\16\0030"..., 1024) = 1024
+lseek(3, 150528, SEEK_SET) = 150528
+write(3, "\r\0\0\0\n\0F\0\3\241\3A\2\341\2\202\2\"\1\303\1d\1\5\0\245\0F\0\0\0\0"..., 1024) = 1024
+lseek(3, 162816, SEEK_SET) = 162816
+write(3, "\r\0\0\0\5\2\"\0\3\241\3B\2\342\2\202\2\"\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(3, 163840, SEEK_SET) = 163840
+write(3, "\r\3(\0\3\0\264\0\1\301\2\244\0\264\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+close(6) = 0
+unlink("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal") = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=2}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/aprx-manual.pdf", {st_mode=S_IFREG|0664, st_size=438348, ...}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\243\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\243\0\0\0\241\0\0\0v\0\0\0\1", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741825, len=1}) = 0
+stat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db", {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+stat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db", {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+open("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", O_RDWR|O_CREAT|O_CLOEXEC, 0644) = 6
+fstat(6, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
+geteuid() = 530
+fstat(6, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
+lseek(6, 0, SEEK_SET) = 0
+write(6, "\331\325\5\371 \241c\327\377\377\377\377{)\253\354\0\0\0\241\0\0\2\0\0\0\4\0\0\0\0\0"..., 512) = 512
+lseek(6, 512, SEEK_SET) = 512
+write(6, "\0\0\0t", 4) = 4
+lseek(6, 516, SEEK_SET) = 516
+write(6, "\n\0\0\0\21\1\265\0\1\265\1\316\1\360\2\n\2-\2L\2k\2\234\2\315\2\357\3\16\0030"..., 1024) = 1024
+lseek(6, 1540, SEEK_SET) = 1540
+write(6, "{)\254f", 4) = 4
+lseek(6, 1544, SEEK_SET) = 1544
+write(6, "\0\0\0d", 4) = 4
+lseek(6, 1548, SEEK_SET) = 1548
+write(6, "\n\0035\0`\1\30\0\3\371\3\362\3\353\3\344\3\335\3\326\3\317\3\310\3\301\3\272\3\263\3\254"..., 1024) = 1024
+lseek(6, 2572, SEEK_SET) = 2572
+write(6, "{)\254\3", 4) = 4
+lseek(6, 2576, SEEK_SET) = 2576
+write(6, "\0\0\0005", 4) = 4
+lseek(6, 2580, SEEK_SET) = 2580
+write(6, "\n\3\226\0\35\0012\2\1d\1|\1\233\0012\1\304\1\333\1\356\2\0\2\33\1\260\2B\2M"..., 1024) = 1024
+lseek(6, 3604, SEEK_SET) = 3604
+write(6, "{)\254\315", 4) = 4
+lseek(6, 3608, SEEK_SET) = 3608
+write(6, "\0\0\0h", 4) = 4
+lseek(6, 3612, SEEK_SET) = 3612
+write(6, "\r\2\350\0\2\0\311\0\0\311\1\341\2\350\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(6, 4636, SEEK_SET) = 4636
+write(6, "{)\254S", 4) = 4
+lseek(6, 4640, SEEK_SET) = 4640
+write(6, "\0\0\0\30", 4) = 4
+lseek(6, 4644, SEEK_SET) = 4644
+write(6, "\5\3~\0$\3\25\f\0\0\0\241\3\265\3\260\3L\3X\3\246\3\241\3\234\3\227\3\222\3\210"..., 1024) = 1024
+lseek(6, 5668, SEEK_SET) = 5668
+write(6, "{)\254\26", 4) = 4
+lseek(3, 139264, SEEK_SET) = 139264
+read(3, "\r\2i\0\2\1\237\0\1\237\0030\0039\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(6, 5672, SEEK_SET) = 5672
+write(6, "\0\0\0\211", 4) = 4
+lseek(6, 5676, SEEK_SET) = 5676
+write(6, "\r\2i\0\2\1\237\0\1\237\0030\0039\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(6, 6700, SEEK_SET) = 6700
+write(6, "{)\254\2", 4) = 4
+lseek(6, 6704, SEEK_SET) = 6704
+write(6, "\0\0\0\1", 4) = 4
+lseek(6, 6708, SEEK_SET) = 6708
+write(6, "SQLite format 3\0\4\0\1\1\0@ \0\0\5\243\0\0\0\241"..., 1024) = 1024
+lseek(6, 7732, SEEK_SET) = 7732
+write(6, "{)\253\354", 4) = 4
+lseek(6, 7736, SEEK_SET) = 7736
+write(6, "\0\0\0\222", 4) = 4
+lseek(6, 7740, SEEK_SET) = 7740
+write(6, "\r\0\0\0\2\2\202\0\2\202\3\17\3\17\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(6, 8764, SEEK_SET) = 8764
+write(6, "{)\254\32", 4) = 4
+lseek(3, 119808, SEEK_SET) = 119808
+read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(6, 8768, SEEK_SET) = 8768
+write(6, "\0\0\0v", 4) = 4
+lseek(6, 8772, SEEK_SET) = 8772
+write(6, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(6, 9796, SEEK_SET) = 9796
+write(6, "{)\253\354", 4) = 4
+lseek(6, 9800, SEEK_SET) = 9800
+write(6, "\0\0\0\217", 4) = 4
+lseek(6, 9804, SEEK_SET) = 9804
+write(6, "\r\0\0\0\n\0F\0\3\241\3A\2\342\2\203\2#\1\303\1d\1\5\0\246\0F\0\0\0\0"..., 1024) = 1024
+lseek(6, 10828, SEEK_SET) = 10828
+write(6, "{)\254\372", 4) = 4
+lseek(6, 10832, SEEK_SET) = 10832
+write(6, "\0\0\0\241", 4) = 4
+lseek(6, 10836, SEEK_SET) = 10836
+write(6, "\r\3(\0\3\0\264\0\1\301\2\244\0\264\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(6, 11860, SEEK_SET) = 11860
+write(6, "{)\254\376", 4) = 4
+lseek(6, 11864, SEEK_SET) = 11864
+write(6, "\0\0\0\240", 4) = 4
+lseek(6, 11868, SEEK_SET) = 11868
+write(6, "\r\0\0\0\5\2\"\0\3\241\3B\2\342\2\202\2\"\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(6, 12892, SEEK_SET) = 12892
+write(6, "{)\254W", 4) = 4
+lseek(6, 12896, SEEK_SET) = 12896
+write(6, "\0\0\0\25", 4) = 4
+lseek(6, 12900, SEEK_SET) = 12900
+write(6, "\r\0\0\0\1\3\331\0\3\331\2\230\2\230\2\230\2\230\2\230\2\230\2\230\2\230\1\232\1\232\1\232"..., 1024) = 1024
+lseek(6, 13924, SEEK_SET) = 13924
+write(6, "{)\253\356", 4) = 4
+lseek(6, 13928, SEEK_SET) = 13928
+write(6, "\0\0\0\4", 4) = 4
+lseek(6, 13932, SEEK_SET) = 13932
+write(6, "\r\0\0\0\3\3\325\0\3\361\3\346\3\325\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(6, 14956, SEEK_SET) = 14956
+write(6, "{)\253\354", 4) = 4
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+lseek(3, 0, SEEK_SET) = 0
+write(3, "SQLite format 3\0\4\0\1\1\0@ \0\0\5\244\0\0\0\241"..., 1024) = 1024
+lseek(3, 3072, SEEK_SET) = 3072
+write(3, "\r\0\0\0\3\3\325\0\3\361\3\346\3\325\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(3, 20480, SEEK_SET) = 20480
+write(3, "\r\0\0\0\2\3\262\0\3\331\3\262\2\230\2\230\2\230\2\230\2\230\2\230\2\230\1\232\1\232\1\232"..., 1024) = 1024
+lseek(3, 23552, SEEK_SET) = 23552
+write(3, "\5\3~\0#\3\25\f\0\0\0\241\3\265\3\260\3L\3X\3\246\3\241\3\234\3\227\3\222\3\210"..., 1024) = 1024
+lseek(3, 53248, SEEK_SET) = 53248
+write(3, "\n\3\226\0\35\0012\2\1d\1|\1\233\0012\1\304\1\333\1\356\2\0\2\33\1\260\2B\2M"..., 1024) = 1024
+lseek(3, 101376, SEEK_SET) = 101376
+write(3, "\n\0035\0`\1\30\0\3\371\3\362\3\353\3\344\3\335\3\326\3\317\3\310\3\301\3\272\3\263\3\254"..., 1024) = 1024
+lseek(3, 105472, SEEK_SET) = 105472
+write(3, "\r\0\0\0\3\1_\0\1_\2)\2\371\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(3, 117760, SEEK_SET) = 117760
+write(3, "\n\0\0\0\21\1\265\0\1\265\1\316\1\360\2\n\2-\2L\2k\2\234\2\315\2\357\3\16\0030"..., 1024) = 1024
+lseek(3, 119808, SEEK_SET) = 119808
+write(3, "\0\0\0\0\0\0\0\1\0\0\0\222\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(3, 139264, SEEK_SET) = 139264
+write(3, "\r\0\0\0\2\2\202\0\2\202\3\17\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(3, 145408, SEEK_SET) = 145408
+write(3, "\r\0\0\0\n\0F\0\3\241\3A\2\342\2\203\2#\1\303\1d\1\5\0\246\0F\0\0\0\0"..., 1024) = 1024
+lseek(3, 148480, SEEK_SET) = 148480
+write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(3, 162816, SEEK_SET) = 162816
+write(3, "\r\0\0\0\5\2\"\0\3\241\3B\2\342\2\202\2\"\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(3, 163840, SEEK_SET) = 163840
+write(3, "\r\0\0\0\4\0\177\0\3\35\2\231\1\214\0\177\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+close(6) = 0
+unlink("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal") = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=2}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\244\0\0\0\241\0\0\0v\0\0\0\2", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/aprx-manual.odt", {st_mode=S_IFREG|0664, st_size=197896, ...}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\244\0\0\0\241\0\0\0v\0\0\0\2", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\244\0\0\0\241\0\0\0v\0\0\0\2", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\244\0\0\0\241\0\0\0v\0\0\0\2", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\244\0\0\0\241\0\0\0v\0\0\0\2", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/aprx-manual.odt", {st_mode=S_IFREG|0664, st_size=197896, ...}) = 0
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/aprx-manual.odt", {st_mode=S_IFREG|0664, st_size=197896, ...}) = 0
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/aprx-manual.odt", {st_mode=S_IFREG|0664, st_size=197896, ...}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\244\0\0\0\241\0\0\0v\0\0\0\2", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741825, len=1}) = 0
+stat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db", {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+stat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db", {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+open("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", O_RDWR|O_CREAT|O_CLOEXEC, 0644) = 6
+fstat(6, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
+geteuid() = 530
+fstat(6, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
+lseek(6, 0, SEEK_SET) = 0
+write(6, "\331\325\5\371 \241c\327\377\377\377\377h\201\256\326\0\0\0\241\0\0\2\0\0\0\4\0\0\0\0\0"..., 512) = 512
+lseek(6, 512, SEEK_SET) = 512
+write(6, "\0\0\0\241", 4) = 4
+lseek(6, 516, SEEK_SET) = 516
+write(6, "\r\0\0\0\4\0\177\0\3\35\2\231\1\214\0\177\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(6, 1540, SEEK_SET) = 1540
+write(6, "h\201\257{", 4) = 4
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+lseek(6, 1544, SEEK_SET) = 1544
+write(6, "\0\0\0\1", 4) = 4
+lseek(6, 1548, SEEK_SET) = 1548
+write(6, "SQLite format 3\0\4\0\1\1\0@ \0\0\5\244\0\0\0\241"..., 1024) = 1024
+lseek(6, 2572, SEEK_SET) = 2572
+write(6, "h\201\256\326", 4) = 4
+lseek(3, 0, SEEK_SET) = 0
+write(3, "SQLite format 3\0\4\0\1\1\0@ \0\0\5\245\0\0\0\241"..., 1024) = 1024
+lseek(3, 163840, SEEK_SET) = 163840
+write(3, "\r\0\0\0\4\0t\0\3\35\2\231\0t\1\214\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+close(6) = 0
+unlink("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal") = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=2}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\245\0\0\0\241\0\0\0v\0\0\0\2", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741825, len=1}) = 0
+stat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db", {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+stat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db", {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+open("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", O_RDWR|O_CREAT|O_CLOEXEC, 0644) = 6
+fstat(6, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
+geteuid() = 530
+fstat(6, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
+lseek(6, 0, SEEK_SET) = 0
+write(6, "\331\325\5\371 \241c\327\377\377\377\377\360IZ\206\0\0\0\241\0\0\2\0\0\0\4\0\0\0\0\0"..., 512) = 512
+lseek(6, 512, SEEK_SET) = 512
+write(6, "\0\0\0\25", 4) = 4
+lseek(6, 516, SEEK_SET) = 516
+write(6, "\r\0\0\0\2\3\262\0\3\331\3\262\2\230\2\230\2\230\2\230\2\230\2\230\2\230\1\232\1\232\1\232"..., 1024) = 1024
+lseek(6, 1540, SEEK_SET) = 1540
+write(6, "\360IZ\210", 4) = 4
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+lseek(6, 1544, SEEK_SET) = 1544
+write(6, "\0\0\0\1", 4) = 4
+lseek(6, 1548, SEEK_SET) = 1548
+write(6, "SQLite format 3\0\4\0\1\1\0@ \0\0\5\245\0\0\0\241"..., 1024) = 1024
+lseek(6, 2572, SEEK_SET) = 2572
+write(6, "\360IZ\206", 4) = 4
+lseek(3, 0, SEEK_SET) = 0
+write(3, "SQLite format 3\0\4\0\1\1\0@ \0\0\5\246\0\0\0\241"..., 1024) = 1024
+lseek(3, 20480, SEEK_SET) = 20480
+write(3, "\r\3\331\0\1\3\262\0\3\262\3\262\2\230\2\230\2\230\2\230\2\230\2\230\2\230\1\232\1\232\1\232"..., 1024) = 1024
+close(6) = 0
+unlink("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal") = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=2}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/aprx-manual.pdf", {st_mode=S_IFREG|0664, st_size=438348, ...}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\246\0\0\0\241\0\0\0v\0\0\0\2", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\246\0\0\0\241\0\0\0v\0\0\0\2", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\246\0\0\0\241\0\0\0v\0\0\0\2", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\246\0\0\0\241\0\0\0v\0\0\0\2", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/aprx-manual.pdf", {st_mode=S_IFREG|0664, st_size=438348, ...}) = 0
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/aprx-manual.pdf", {st_mode=S_IFREG|0664, st_size=438348, ...}) = 0
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/doc/aprx-manual.pdf", {st_mode=S_IFREG|0664, st_size=438348, ...}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\246\0\0\0\241\0\0\0v\0\0\0\2", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741825, len=1}) = 0
+stat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db", {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+stat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db", {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+open("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", O_RDWR|O_CREAT|O_CLOEXEC, 0644) = 6
+fstat(6, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
+geteuid() = 530
+fstat(6, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
+lseek(6, 0, SEEK_SET) = 0
+write(6, "\331\325\5\371 \241c\327\377\377\377\377\314\357S\363\0\0\0\241\0\0\2\0\0\0\4\0\0\0\0\0"..., 512) = 512
+lseek(6, 512, SEEK_SET) = 512
+write(6, "\0\0\0\241", 4) = 4
+lseek(6, 516, SEEK_SET) = 516
+write(6, "\r\0\0\0\4\0t\0\3\35\2\231\0t\1\214\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(6, 1540, SEEK_SET) = 1540
+write(6, "\314\357T\316", 4) = 4
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+lseek(6, 1544, SEEK_SET) = 1544
+write(6, "\0\0\0\1", 4) = 4
+lseek(6, 1548, SEEK_SET) = 1548
+write(6, "SQLite format 3\0\4\0\1\1\0@ \0\0\5\246\0\0\0\241"..., 1024) = 1024
+lseek(6, 2572, SEEK_SET) = 2572
+write(6, "\314\357S\363", 4) = 4
+lseek(3, 0, SEEK_SET) = 0
+write(3, "SQLite format 3\0\4\0\1\1\0@ \0\0\5\247\0\0\0\241"..., 1024) = 1024
+lseek(3, 163840, SEEK_SET) = 163840
+write(3, "\r\0\0\0\4\0i\0\3\35\2\231\1\201\0i\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+close(6) = 0
+unlink("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal") = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=2}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\247\0\0\0\241\0\0\0v\0\0\0\2", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741825, len=1}) = 0
+stat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db", {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+stat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db", {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+open("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", O_RDWR|O_CREAT|O_CLOEXEC, 0644) = 6
+fstat(6, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
+geteuid() = 530
+fstat(6, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
+lseek(6, 0, SEEK_SET) = 0
+write(6, "\331\325\5\371 \241c\327\377\377\377\377\345u\251\254\0\0\0\241\0\0\2\0\0\0\4\0\0\0\0\0"..., 512) = 512
+lseek(6, 512, SEEK_SET) = 512
+write(6, "\0\0\0\25", 4) = 4
+lseek(6, 516, SEEK_SET) = 516
+write(6, "\r\3\331\0\1\3\262\0\3\262\3\262\2\230\2\230\2\230\2\230\2\230\2\230\2\230\1\232\1\232\1\232"..., 1024) = 1024
+lseek(6, 1540, SEEK_SET) = 1540
+write(6, "\345u\251\256", 4) = 4
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+lseek(6, 1544, SEEK_SET) = 1544
+write(6, "\0\0\0\1", 4) = 4
+lseek(6, 1548, SEEK_SET) = 1548
+write(6, "SQLite format 3\0\4\0\1\1\0@ \0\0\5\247\0\0\0\241"..., 1024) = 1024
+lseek(6, 2572, SEEK_SET) = 2572
+write(6, "\345u\251\254", 4) = 4
+lseek(3, 0, SEEK_SET) = 0
+write(3, "SQLite format 3\0\4\0\1\1\0@ \0\0\5\250\0\0\0\241"..., 1024) = 1024
+lseek(3, 20480, SEEK_SET) = 20480
+write(3, "\r\0\0\0\0\4\0\0\3\262\3\262\2\230\2\230\2\230\2\230\2\230\2\230\2\230\1\232\1\232\1\232"..., 1024) = 1024
+close(6) = 0
+unlink("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal") = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=2}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+lstat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
+select(0, NULL, NULL, NULL, {0, 1000}) = 0 (Timeout)
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\250\0\0\0\241\0\0\0v\0\0\0\2", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+lseek(3, 24, SEEK_SET) = 24
+read(3, "\0\0\5\250\0\0\0\241\0\0\0v\0\0\0\2", 16) = 16
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+access("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-wal", F_OK) = -1 ENOENT (No such file or directory)
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741825, len=1}) = 0
+stat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db", {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+stat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db", {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+open("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal", O_RDWR|O_CREAT|O_CLOEXEC, 0644) = 6
+fstat(6, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
+geteuid() = 530
+fstat(6, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
+lseek(6, 0, SEEK_SET) = 0
+write(6, "\331\325\5\371 \241c\327\377\377\377\377m)H%\0\0\0\241\0\0\2\0\0\0\4\0\0\0\0\0"..., 512) = 512
+lseek(6, 512, SEEK_SET) = 512
+write(6, "\0\0\0\27", 4) = 4
+lseek(6, 516, SEEK_SET) = 516
+write(6, "\n\0\0\0\1\3\373\0\3\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(6, 1540, SEEK_SET) = 1540
+write(6, "m)H%", 4) = 4
+lseek(6, 1544, SEEK_SET) = 1544
+write(6, "\0\0\0\26", 4) = 4
+lseek(6, 1548, SEEK_SET) = 1548
+write(6, "\r\0\0\0\1\3\371\0\3\371\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(6, 2572, SEEK_SET) = 2572
+write(6, "m)H%", 4) = 4
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741824, len=1}) = 0
+fcntl(3, F_SETLK, {type=F_WRLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+lseek(6, 2576, SEEK_SET) = 2576
+write(6, "\0\0\0\1", 4) = 4
+lseek(6, 2580, SEEK_SET) = 2580
+write(6, "SQLite format 3\0\4\0\1\1\0@ \0\0\5\250\0\0\0\241"..., 1024) = 1024
+lseek(6, 3604, SEEK_SET) = 3604
+write(6, "m)H%", 4) = 4
+lseek(3, 0, SEEK_SET) = 0
+write(3, "SQLite format 3\0\4\0\1\1\0@ \0\0\5\251\0\0\0\241"..., 1024) = 1024
+lseek(3, 21504, SEEK_SET) = 21504
+write(3, "\r\0\0\0\0\4\0\0\3\371\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+lseek(3, 22528, SEEK_SET) = 22528
+write(3, "\n\0\0\0\0\4\0\0\3\373\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024) = 1024
+close(6) = 0
+unlink("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db-journal") = 0
+fcntl(3, F_SETLK, {type=F_RDLCK, whence=SEEK_SET, start=1073741826, len=510}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=1073741824, len=2}) = 0
+fcntl(3, F_SETLK, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
+close(5) = 0
+close(-1) = -1 EBADF (Bad file descriptor)
+close(4) = 0
+fstat(3, {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+stat("/net/fileserver.methics.fi/mnt/mirror/common/scratch/mea/ham/aprx/aprx-trunk/.svn/wc.db", {st_mode=S_IFREG|0644, st_size=164864, ...}) = 0
+close(3) = 0
+munmap(0x7fb0c4174000, 212992) = 0
+munmap(0x7fb0c41d2000, 139264) = 0
+munmap(0x7fb0c4152000, 139264) = 0
+exit_group(0) = ?
++++ exited with 0 +++
diff --git a/ttyreader.c b/ttyreader.c
new file mode 100644
index 0000000..888a5d9
--- /dev/null
+++ b/ttyreader.c
@@ -0,0 +1,1080 @@
+/* **************************************************************** *
+ * *
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ * **************************************************************** */
+
+#define _SVID_SOURCE 1
+
+#include "aprx.h"
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in.h>
+
+
+/* The ttyreader does read TTY ports into a big buffer, and then from there
+ to packet frames depending on what is attached... */
+
+
+static struct serialport **ttys;
+static int ttycount; /* How many are defined ? */
+
+#define TTY_OPEN_RETRY_DELAY_SECS 30
+
+static int poll_millis; /* milliseconds (0 = none.) */
+static struct timeval poll_millis_tv;
+
+
+void hexdumpfp(FILE *fp, const uint8_t *buf, const int len, int axaddr)
+{
+ int i, j;
+ for (i = 0, j=1; i < len; ++i,++j) {
+ int c = buf[i] & 0xFF;
+ fprintf(fp, "%02x", c);
+ if (j < 8)
+ fputc(' ',fp);
+ else {
+ fputc('|',fp);
+ j = 0;
+ }
+ }
+ fprintf(fp, " = ");
+ for (i = 0, j = 1; i < len; ++i,++j) {
+ int c = buf[i] & 0xFF;
+ /*
+ if ((c & 0x81) == 0x80 && (i < 8)) {
+ // Auto-trigger AX.25 address plaintext converting
+ axaddr = 1;
+ }
+ */
+ if (axaddr && ((c & 0x01) == 1) && i > 3) {
+ // Definitely not AX.25 address anymore..
+ axaddr = 0;
+ }
+ if (axaddr) {
+ // Shifted AX.25 address byte?
+ c >>= 1;
+ }
+ if (c < 0x20 || c > 0x7E)
+ c = '.';
+ fputc(c, fp);
+ if (j >= 8) {
+ fputc('|',fp);
+ j = 0;
+ }
+ }
+}
+
+
+/*
+ * ttyreader_getc() -- pick one char ( >= 0 ) out of input buffer, or -1 if out of buffer
+ */
+int ttyreader_getc(struct serialport *S)
+{
+ if (S->rdcursor >= S->rdlen) { /* Out of data ? */
+ if (S->rdcursor)
+ S->rdcursor = S->rdlen = 0;
+ /* printf("-\n"); */
+ return -1;
+ }
+
+ /* printf(" %02X", 0xFF & S->rdbuf[S->rdcursor++]); */
+
+ return (0xFF & S->rdbuf[S->rdcursor++]);
+}
+
+
+
+/*
+ * ttyreader_pulltnc2() -- process a line of text by calling
+ * TNC2 UI Monitor analyzer
+ */
+
+static int ttyreader_pulltnc2(struct serialport *S)
+{
+ const uint8_t *p;
+ int addrlen = 0;
+ p = memchr(S->rdline, ':', S->rdlinelen);
+ if (p != NULL)
+ addrlen = (int)(p - S->rdline);
+
+ erlang_add(S->ttycallsign[0], ERLANG_RX, S->rdlinelen, 1); /* Account one packet */
+
+ /* Send the frame to internal AX.25 network */
+ /* netax25_sendax25_tnc2(S->rdline, S->rdlinelen); */
+
+#ifndef DISABLE_IGATE
+ /* S->rdline[] has text line without line ending CR/LF chars */
+ igate_to_aprsis(S->ttycallsign[0], 0, (char *) (S->rdline), addrlen, S->rdlinelen, 0, 1);
+#endif
+
+ return 0;
+}
+
+#if 0
+/*
+ * ttyreader_pullaea() -- process a line of text by calling
+ * AEA MONITOR 1 analyzer
+ */
+
+static int ttyreader_pullaea(struct serialport *S)
+{
+ int i;
+
+ if (S->rdline[S->rdlinelen - 1] == ':') {
+ /* Could this be the AX25 header ? */
+ char *s = strchr(S->rdline, '>');
+ if (s) {
+ /* Ah yes, it well could be.. */
+ strcpy(S->rdline2, S->rdline);
+ return;
+ }
+ }
+
+ /* FIXME: re-arrange the S->rdline2 contained AX25 address tokens
+ and flags..
+
+ perl code:
+ @addrs = split('>', $rdline2);
+ $out = shift @addrs; # pop first token in sequence
+ $out .= '>';
+ $out .= pop @addrs; # pop last token in sequence
+ foreach $a (@addrs) { # rest of the tokens in sequence, if any
+ $out .= ',' . $a;
+ }
+ # now $out has address data in TNC2 sequence.
+ */
+
+ /* printf("%s%s\n", S->rdline2, S->rdline); fflush(stdout); */
+
+ return 0;
+}
+#endif
+
+
+/*
+ * ttyreader_pulltext() -- process a line of text from the serial port..
+ */
+
+static int ttyreader_pulltext(struct serialport *S)
+{
+ int c;
+ const time_t rdtime = S->rdline_time;
+ // "rdtime > now" case ("now" going backwards) is always overwritten below
+
+ if (timecmp(rdtime+2, tick.tv_sec) < 0) {
+ // A timeout has happen? Either data is added constantly, or
+ // nothing was received from TEXT datastream for couple seconds!
+ S->rdlinelen = 0;
+ // S->kissstate = KISSSTATE_SYNCHUNT;
+ }
+ S->rdline_time = tick.tv_sec;
+
+ for (;;) {
+
+ c = ttyreader_getc(S);
+ if (c < 0)
+ return c; /* Out of input.. */
+
+ /* S->kissstate != 0: read data into S->rdline,
+ == 0: discard data until CR|LF.
+ Zero-size read line is discarded as well
+ (only CR|LF on input frame) */
+
+ if (S->kissstate == KISSSTATE_SYNCHUNT) {
+ /* Looking for CR or LF.. */
+ if (c == '\n' || c == '\r')
+ S->kissstate = KISSSTATE_COLLECTING;
+
+ S->rdlinelen = 0;
+ continue;
+ }
+
+ /* Now: (S->kissstate != KISSSTATE_SYNCHUNT) */
+
+ if (c == '\n' || c == '\r') {
+ /* End of line seen! */
+ if (S->rdlinelen > 0) {
+
+ /* Non-zero-size string, put terminating 0 byte on it. */
+ S->rdline[S->rdlinelen] = 0;
+
+ /* .. and process it depending .. */
+
+ if (S->linetype == LINETYPE_TNC2) {
+ ttyreader_pulltnc2(S);
+#if 0
+ } else { /* .. it is LINETYPE_AEA ? */
+ ttyreader_pullaea(S);
+#endif
+ }
+ }
+ S->rdlinelen = 0;
+ continue;
+ }
+
+ /* Now place the char in the linebuffer, if there is space.. */
+ if (S->rdlinelen >= (sizeof(S->rdline) - 3)) { /* Too long ! Way too long ! */
+ S->kissstate = KISSSTATE_SYNCHUNT; /* Sigh.. discard it. */
+ S->rdlinelen = 0;
+ continue;
+ }
+
+ /* Put it on line store: */
+ S->rdline[S->rdlinelen++] = c;
+
+ } /* .. input loop */
+
+ return 0; /* not reached */
+}
+
+
+
+/*
+ * ttyreader_linewrite() -- write out buffered data
+ */
+void ttyreader_linewrite(struct serialport *S)
+{
+ int i, len;
+
+ if ((S->wrlen == 0) || (S->wrlen > 0 && S->wrcursor >= S->wrlen)) {
+ S->wrlen = S->wrcursor = 0; /* already all written */
+ return;
+ }
+
+ /* Now there is some data in between wrcursor and wrlen */
+
+ len = S->wrlen - S->wrcursor;
+ if (len > 0)
+ i = write(S->fd, S->wrbuf + S->wrcursor, len);
+ else
+ i = 0;
+ if (i > 0) { /* wrote something */
+ S->wrcursor += i;
+ len = S->wrlen - S->wrcursor;
+ if (len == 0) {
+ S->wrcursor = S->wrlen = 0; /* wrote all ! */
+ } else {
+ /* compact the buffer a bit */
+ memcpy(S->wrbuf, S->wrbuf + S->wrcursor, len);
+ S->wrcursor = 0;
+ S->wrlen = len;
+ }
+ }
+}
+
+
+/*
+ * ttyreader_lineread() -- read what there is into our buffer,
+ * and process the buffer..
+ */
+
+static void ttyreader_lineread(struct serialport *S)
+{
+ int i;
+
+ int rdspace = sizeof(S->rdbuf) - S->rdlen;
+
+ if (S->rdcursor > 0) {
+ /* Read-out cursor is not at block beginning,
+ is there unread data too ? */
+ if (S->rdlen > S->rdcursor) {
+ /* Uh.. lets move buffer down a bit,
+ to make room for more to the end.. */
+ memcpy(S->rdbuf, S->rdbuf + S->rdcursor,
+ S->rdlen - S->rdcursor);
+ S->rdlen = S->rdlen - S->rdcursor;
+ } else
+ S->rdlen = 0; /* all processed, mark its size zero */
+ /* Cursor to zero, rdspace recalculated */
+ S->rdcursor = 0;
+
+ /* recalculate */
+ rdspace = sizeof(S->rdbuf) - S->rdlen;
+ }
+
+ if (rdspace > 0) { /* We have room to read into.. */
+ i = read(S->fd, S->rdbuf + S->rdlen, rdspace);
+ if (i == 0) { /* EOF ? USB unplugged ? */
+ close(S->fd);
+ S->fd = -1;
+ tv_timeradd_seconds(&S->wait_until, &tick, TTY_OPEN_RETRY_DELAY_SECS);
+ aprxlog("TTY %s EOF - CLOSED, WAITING %d SECS\n", S->ttyname, TTY_OPEN_RETRY_DELAY_SECS);
+ return;
+ }
+ if (i < 0) /* EAGAIN or whatever.. */
+ return;
+
+ /* Some data has been accumulated ! */
+ if (debug > 2) {
+ printf("%ld\tTTY %s: read() frame: ", tick.tv_sec, S->ttyname);
+ hexdumpfp(stdout, S->rdbuf+S->rdlen, i, 1);
+ printf("\n");
+ }
+
+ S->rdlen += i;
+ S->last_read_something = tick.tv_sec;
+ }
+
+ /* Done reading, maybe. Now processing.
+ The pullXX does read up all input, and does
+ however many frames there are in, and pauses
+ when there is no enough input data for a full
+ frame/line/whatever.
+ */
+
+ if (S->linetype == LINETYPE_KISS ||
+ S->linetype == LINETYPE_KISSFLEXNET ||
+ S->linetype == LINETYPE_KISSBPQCRC ||
+ S->linetype == LINETYPE_KISSSMACK) {
+
+ kiss_pullkiss(S);
+
+
+#ifndef DISABLE_IGATE
+ } else if (S->linetype == LINETYPE_DPRSGW) {
+
+ dprsgw_pulldprs(S);
+
+#endif
+ } else if (S->linetype == LINETYPE_TNC2
+#if 0
+ || S->linetype == LINETYPE_AEA
+#endif
+ ) {
+
+ ttyreader_pulltext(S);
+
+ } else {
+ close(S->fd); /* Urgh ?? Bad linetype value ?? */
+ S->fd = -1;
+ tv_timeradd_seconds(&S->wait_until, &tick, TTY_OPEN_RETRY_DELAY_SECS);
+ aprxlog("TTY %s Unsupported linetype - CLOSED, WAITING %d SECS\n", S->ttyname, TTY_OPEN_RETRY_DELAY_SECS);
+ }
+
+ /* Consumed something, and our read cursor is not in the beginning ? */
+ if (S->rdcursor > 0 && S->rdcursor < S->rdlen) {
+ /* Compact the input buffer! */
+ memcpy(S->rdbuf, S->rdbuf + S->rdcursor,
+ S->rdlen - S->rdcursor);
+ }
+ S->rdlen -= S->rdcursor;
+ S->rdcursor = 0;
+}
+
+
+/*
+ * ttyreader_linesetup() -- open and configure the serial port
+ */
+
+static void ttyreader_linesetup(struct serialport *S)
+{
+ int i;
+
+ S->wait_until.tv_sec = 0; // Zero it just to be safe
+ S->wait_until.tv_usec = 0; // Zero it just to be safe
+
+ S->wrlen = S->wrcursor = 0; // init them at first
+
+ // If NOT tcp! type socket, it is presumably openable with
+ // open(2) instead of something else, like socket(2)...
+ if (memcmp(S->ttyname, "tcp!", 4) != 0) {
+ int e;
+ // Open the serial port as RW, non-blocking, no-control-tty
+ S->fd = open(S->ttyname, O_RDWR | O_NOCTTY | O_NONBLOCK, 0);
+ e = errno;
+
+ if (debug) {
+ printf("%ld\tTTY %s OPEN - fd=%d - ",
+ tick.tv_sec, S->ttyname, S->fd);
+ if (S->fd < 0) {
+ printf("errno=%d (%s) - ", e, strerror(e));
+ }
+ }
+ if (S->fd < 0) { /* Urgh.. an error.. */
+ tv_timeradd_seconds(&S->wait_until, &tick, TTY_OPEN_RETRY_DELAY_SECS);
+ if (debug)
+ printf("FAILED, WAITING %d SECS\n",
+ TTY_OPEN_RETRY_DELAY_SECS);
+ aprxlog("TTY %s failed to open; errno=%d (%s)",
+ S->ttyname, e, strerror(e));
+ return;
+ }
+ if (debug)
+ printf("OK\n");
+
+ aprxlog("TTY %s Opened.\n", S->ttyname);
+
+
+ /* Set attributes */
+ aprx_cfmakeraw(&S->tio, 1); /* hw-flow on */
+ i = tcsetattr(S->fd, TCSAFLUSH, &S->tio);
+
+ if (i < 0) {
+ if (debug)
+ printf("%ld\tERROR: TCSETATTR failed; errno=%d\n",
+ tick.tv_sec, errno);
+ close(S->fd);
+ S->fd = -1;
+ tv_timeradd_seconds(&S->wait_until, &tick, TTY_OPEN_RETRY_DELAY_SECS);
+ aprxlog("TTY %s tcsetattr() failed. CLOSING TTY.\n", S->ttyname);
+ return;
+ }
+ // FIXME: ?? Set baud-rates ?
+ // Used system (Linux) has them in 'struct termios' so they
+ // are now set, but other systems may have different ways..
+
+ // Flush buffers once again.
+ i = tcflush(S->fd, TCIOFLUSH);
+
+ for (i = 0; i < 16; ++i) {
+ if (S->initstring[i] != NULL) {
+ memcpy(S->wrbuf + S->wrlen, S->initstring[i], S->initlen[i]);
+ S->wrlen += S->initlen[i];
+ }
+ }
+
+ /* Flush it out.. and if not successfull,
+ poll(2) will take care of it soon enough.. */
+ ttyreader_linewrite(S);
+
+ } else { /* socket connection to remote TTY.. */
+ /* "tcp!hostname-or-ip!port!opt-parameters" */
+ char *par = strdup(S->ttyname);
+ char *host = NULL, *port = NULL, *opts = NULL;
+ struct addrinfo req, *ai;
+ int i;
+
+ if (debug)
+ printf("socket connect() preparing: %s\n", par);
+
+ while (1) {
+ host = strchr(par, '!');
+ if (host)
+ ++host;
+ else
+ break; /* Found no '!' ! */
+ port = strchr(host, '!');
+ if (port)
+ *port++ = 0;
+ else
+ break; /* Found no '!' ! */
+ opts = strchr(port, '!');
+ if (opts)
+ *opts++ = 0;
+ break;
+ }
+
+ if (!port) {
+ /* Still error condition.. no port data */
+ }
+
+ memset(&req, 0, sizeof(req));
+ req.ai_socktype = SOCK_STREAM;
+ req.ai_protocol = IPPROTO_TCP;
+ req.ai_flags = 0;
+#if 1
+ req.ai_family = AF_UNSPEC; /* IPv4 and IPv6 are both OK */
+#else
+ req.ai_family = AF_INET; /* IPv4 only */
+#endif
+ ai = NULL;
+
+ i = getaddrinfo(host, port, &req, &ai);
+
+ if (ai) {
+ S->fd = socket(ai->ai_family, SOCK_STREAM, 0);
+ if (S->fd >= 0) {
+
+ fd_nonblockingmode(S->fd);
+
+ i = connect(S->fd, ai->ai_addr,
+ ai->ai_addrlen);
+ if ((i != 0) && (errno != EINPROGRESS)) {
+ /* non-blocking connect() yields EINPROGRESS,
+ anything else and we fail entirely... */
+ if (debug)
+ printf("ttyreader socket connect call failed: %d : %s\n", errno, strerror(errno));
+ close(S->fd);
+ S->fd = -1;
+ aprxlog("TTY %s Socket open failed.\n", S->ttyname);
+ }
+ }
+
+ freeaddrinfo(ai);
+ }
+ free(par);
+ }
+
+ S->last_read_something = tick.tv_sec; /* mark the timeout for future.. */
+
+ S->rdlen = S->rdcursor = S->rdlinelen = 0;
+ S->kissstate = KISSSTATE_SYNCHUNT;
+
+ memset( S->smack_probe, 0, sizeof(S->smack_probe) );
+ S->smack_subids = 0;
+}
+
+/*
+ * ttyreader_init()
+ */
+
+void ttyreader_init(void)
+{
+ /* nothing.. */
+}
+
+
+
+/*
+ * ttyreader_prepoll() -- prepare system for next round of polling
+ */
+
+int ttyreader_prepoll(struct aprxpolls *app)
+{
+ int idx = 0; /* returns number of *fds filled.. */
+ int i;
+ struct serialport *S;
+ struct pollfd *pfd;
+
+ if (poll_millis_tv.tv_sec == 0) {
+ poll_millis_tv = tick;
+ }
+
+ // if (debug) printf("ttyreader_prepoll() %d\n", poll_millis);
+ for (i = 0; i < ttycount; ++i) {
+ S = ttys[i];
+ if (!S->ttyname)
+ continue; /* No name, no look... */
+
+
+#if 0 // occasional debug mode without real hardware at hand
+ if (poll_millis > 0) {
+ int deltams = tv_timerdelta_millis(&tick, &poll_millis_tv);
+ struct timeval tv;
+ if (debug) printf("%d.%06d .. defining %d ms KISS POLL\n", tick.tv_sec, tick.tv_usec, poll_millis);
+ }
+#endif
+
+ if (S->fd < 0) {
+ if (time_reset && (S->wait_until.tv_sec != 0)) {
+ // System time jumped, reset it to NOW.
+ S->wait_until = tick;
+ }
+
+ /* Not an open TTY, but perhaps waiting ? */
+ if ((S->wait_until.tv_sec != 0) && tv_timercmp( &S->wait_until, &tick) > 0) {
+ /* .. waiting for future! */
+ if (tv_timercmp( &app->next_timeout, &S->wait_until ) > 0) {
+ app->next_timeout = S->wait_until;
+ }
+ /* .. but only until our timeout,
+ if it is sooner than global one. */
+ continue; /* Waiting on this one.. */
+ }
+
+ /* Waiting or not, FD is not open, and deadline is past.
+ Lets try to open! */
+
+ ttyreader_linesetup(S);
+
+ }
+ /* .. No open FD */
+ /* Still no open FD ? */
+ if (S->fd < 0)
+ continue;
+
+ // FD is open, check read/idle timeout ...
+ if (time_reset) {
+ // System time has jumped, Reset the read time to NOW.
+ S->last_read_something = tick.tv_sec;
+ }
+
+ // FD is open, check read/idle timeout ...
+ if ((S->read_timeout > 0) &&
+ timecmp(tick.tv_sec, (S->last_read_something + S->read_timeout)) > 0) {
+ if (debug)
+ printf("%ld\tRead timeout on %s; %d seconds w/o input. fd=%d\n",
+ tick.tv_sec, S->ttyname, S->read_timeout, S->fd);
+ close(S->fd); /* Close and mark for re-open */
+ S->fd = -1;
+ tv_timeradd_seconds( &S->wait_until, &tick, TTY_OPEN_RETRY_DELAY_SECS);
+ aprxlog("TTY %s read timeout. Closing TTY for later re-open.\n", S->ttyname);
+ continue;
+ }
+
+
+ if (poll_millis > 0) {
+ int margin = poll_millis*2;
+ // Limit large delta time to within 0..2*poll_millis.
+ int deltams = tv_timerdelta_millis(&tick, &poll_millis_tv);
+ if (deltams > margin) deltams = poll_millis;
+ if (deltams < -margin) deltams = poll_millis;
+ tv_timeradd_millis(&poll_millis_tv, &tick, deltams);
+
+ if (debug) printf("%ld.%06d .. defining %d ms KISS POLL\n", (long)tick.tv_sec, (int)tick.tv_usec, poll_millis);
+ }
+
+ /* FD is open, lets mark it for poll read.. */
+ pfd = aprxpolls_new(app);
+ pfd->fd = S->fd;
+ pfd->events = POLLIN | POLLPRI;
+ pfd->revents = 0;
+ if (S->wrlen > 0 && S->wrlen > S->wrcursor)
+ pfd->events |= POLLOUT;
+
+ ++idx;
+ }
+ return idx;
+}
+
+
+/*
+ * ttyreader_postpoll() -- Done polling, what happened ?
+ */
+
+int ttyreader_postpoll(struct aprxpolls *app)
+{
+ int idx, i;
+
+ struct serialport *S;
+ struct pollfd *P;
+
+ // if (debug) printf("ttyreader_postpoll()\n");
+
+ for (idx = 0, P = app->polls; idx < app->pollcount; ++idx, ++P) {
+
+ // Are we operating in active KISS polling mode?
+ if (poll_millis > 0) {
+ for (i = 0; i < ttycount; ++i) {
+ S = ttys[i];
+
+#if 0 // occasional debug mode without real hardware at hand
+ if (tv_timercmp(&poll_millis_tv, &tick) <= 0) {
+ // Poll interval gone, time for next active POLL request!
+ kiss_poll(S);
+ tv_timeradd_millis(&poll_millis_tv, &poll_millis_tv, poll_millis);
+ }
+#endif
+
+ if (S->fd != P->fd)
+ continue; /* Not this one ? */
+ if (S->fd < 0)
+ continue; /* Not this one ? */
+
+ if (!(S->linetype == LINETYPE_KISS ||
+ S->linetype == LINETYPE_KISSFLEXNET ||
+ S->linetype == LINETYPE_KISSBPQCRC ||
+ S->linetype == LINETYPE_KISSSMACK)) {
+ // Not a KISS line..
+ continue;
+ }
+ if (tv_timercmp(&poll_millis_tv, &tick) <= 0) {
+ // Poll interval gone, time for next active POLL request!
+ kiss_poll(S);
+ tv_timeradd_millis(&poll_millis_tv, &poll_millis_tv, poll_millis);
+ }
+ }
+ }
+
+ for (i = 0; i < ttycount; ++i) {
+ S = ttys[i];
+ if (S->fd != P->fd)
+ continue; /* Not this one ? */
+ /* It is this one! */
+
+ if (P->revents & POLLOUT)
+ ttyreader_linewrite(S);
+
+ if (P->revents & (POLLIN | POLLPRI | POLLERR | POLLHUP))
+ ttyreader_lineread(S);
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Make a pre-existing termios structure into "raw" mode: character-at-a-time
+ * mode with no characters interpreted, 8-bit data path.
+ */
+void
+aprx_cfmakeraw(t, f)
+ struct termios *t;
+{
+
+ t->c_iflag &= ~(IMAXBEL|IXOFF|INPCK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON|IGNPAR);
+ t->c_iflag |= IGNBRK;
+
+ t->c_oflag &= ~OPOST;
+ if (f) {
+ t->c_oflag |= CRTSCTS;
+ } else {
+ t->c_oflag &= ~CRTSCTS;
+ }
+
+ t->c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|ICANON|ISIG|IEXTEN|NOFLSH|TOSTOP|PENDIN);
+ t->c_cflag &= ~(CSIZE|PARENB);
+ t->c_cflag |= CS8|CREAD;
+ t->c_cc[VMIN] = 80;
+ t->c_cc[VTIME] = 3;
+}
+
+struct serialport *ttyreader_new(void)
+{
+ struct serialport *tty = calloc(1, sizeof(*tty));
+ int baud = B1200;
+
+ tty->fd = -1;
+ tv_timeradd_seconds( &tty->wait_until, &tick, -1); /* begin opening immediately */
+ tty->last_read_something = tick.tv_sec; /* well, not really.. */
+ tty->linetype = LINETYPE_KISS; /* default */
+ tty->kissstate = KISSSTATE_SYNCHUNT;
+ tty->read_timeout = 3600; /* Default port read timeout is 60 minutes. */
+
+ tty->ttyname = NULL;
+
+
+ /* setup termios parameters for this line.. */
+ aprx_cfmakeraw(&tty->tio, 0);
+ tty->tio.c_cc[VMIN] = 80; /* pick at least one char .. */
+ tty->tio.c_cc[VTIME] = 3; /* 0.3 seconds timeout - 36 chars @ 1200 baud */
+ tty->tio.c_cflag |= (CREAD | CLOCAL);
+
+ cfsetispeed(&tty->tio, baud);
+ cfsetospeed(&tty->tio, baud);
+
+ return tty;
+}
+
+/*
+ * Parse tty related parameters, return 0 for OK, 1 for error
+ */
+int ttyreader_parse_nullparams(struct configfile *cf, struct serialport *tty, char *str)
+{
+ char *param1 = 0;
+ int has_fault = 0;
+
+ /* FIXME: analyze correct serial port data and parity format settings,
+ now hardwired to 8-n-1 -- does not work without for KISS anyway.. */
+
+ config_STRLOWER(str); /* until end of line */
+
+ /* Optional parameters */
+ while (*str != 0) {
+ param1 = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ if (debug)
+ printf(" .. param='%s'",param1);
+
+
+ /* Note: param1 is now lower-case string */
+
+ if (strcmp(param1, "pollmillis") == 0) {
+ param1 = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+ tty->poll_millis = atol(param1); // milliseconds
+ if (poll_millis == 0)
+ poll_millis = tty->poll_millis;
+ if (tty->poll_millis < poll_millis)
+ poll_millis = tty->poll_millis;
+ if (poll_millis < 1 || poll_millis > 10000) {
+ has_fault = 1;
+ printf("%s:%d POLLMILLIS value not in sanity range of 1 to 10 000: '%s'", cf->name, cf->linenum, param1);
+ } else {
+ if (debug)
+ printf(" .. pollmillis %d -- polling interval\n", tty->poll_millis);
+ }
+
+ } else {
+ printf("%s:%d ERROR: Unknown sub-keyword on a serial/tcp device configuration: '%s'\n",
+ cf->name, cf->linenum, param1);
+ has_fault = 1;
+ }
+ }
+ if (debug) printf("\n");
+ return has_fault;
+}
+
+/*
+ * Parse tty related parameters, return 0 for OK, 1 for error
+ */
+int ttyreader_parse_ttyparams(struct configfile *cf, struct serialport *tty, char *str)
+{
+ int i;
+ speed_t baud;
+ int tncid = 0;
+ char *param1 = 0;
+ int has_fault = 0;
+
+ /* FIXME: analyze correct serial port data and parity format settings,
+ now hardwired to 8-n-1 -- does not work without for KISS anyway.. */
+
+ config_STRLOWER(str); /* until end of line */
+
+ /* Optional parameters */
+ while (*str != 0) {
+ param1 = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ if (debug)
+ printf(" .. param='%s'",param1);
+
+ /* See if it is baud-rate ? */
+ i = atol(param1); /* serial port speed - baud rate */
+ baud = B1200;
+ switch (i) {
+ case 1200:
+ baud = B1200;
+ break;
+#ifdef B1800
+ case 1800:
+ baud = B1800;
+ break;
+#endif
+ case 2400:
+ baud = B2400;
+ break;
+ case 4800:
+ baud = B4800;
+ break;
+ case 9600:
+ baud = B9600;
+ break;
+#ifdef B19200
+ case 19200:
+ baud = B19200;
+ break;
+#endif
+#ifdef B38400
+ case 38400:
+ baud = B38400;
+ break;
+#endif
+#ifdef B57600
+ case 57600:
+ baud = B57600;
+ break;
+#endif
+#ifdef B115200
+ case 115200:
+ baud = B115200;
+ break;
+#endif
+#ifdef B230400
+ case B230400:
+ baud = B230400;
+ break;
+#endif
+#ifdef B460800
+ case 460800:
+ baud = B460800;
+ break;
+#endif
+#ifdef B500000
+ case 500000:
+ baud = B500000;
+ break;
+#endif
+#ifdef B576000
+ case 576000:
+ baud = B576000;
+ break;
+#endif
+ default:
+ i = -1;
+ break;
+ }
+ if (baud != B1200) {
+ cfsetispeed(&tty->tio, baud);
+ cfsetospeed(&tty->tio, baud);
+ }
+
+ /* Note: param1 is now lower-case string */
+
+ if (i > 0) {
+ ;
+ } else if (strcmp(param1, "8n1") == 0) {
+ /* default behaviour, ignore */
+ } else if (strcmp(param1, "kiss") == 0) {
+ tty->linetype = LINETYPE_KISS; /* plain basic KISS */
+
+ } else if (strcmp(param1, "xorsum") == 0) {
+ tty->linetype = LINETYPE_KISSBPQCRC; /* KISS with BPQ "CRC" */
+ } else if (strcmp(param1, "xkiss") == 0) {
+ tty->linetype = LINETYPE_KISSBPQCRC; /* KISS with BPQ "CRC" */
+ } else if (strcmp(param1, "bpqcrc") == 0) {
+ tty->linetype = LINETYPE_KISSBPQCRC; /* KISS with BPQ "CRC" */
+
+ } else if (strcmp(param1, "flexnet") == 0) {
+ tty->linetype = LINETYPE_KISSFLEXNET; /* KISS with FLEXNET's CRC16 */
+ } else if (strcmp(param1, "smack") == 0) {
+ tty->linetype = LINETYPE_KISSSMACK; /* KISS with SMACK / CRC16 */
+ } else if (strcmp(param1, "crc16") == 0) {
+ tty->linetype = LINETYPE_KISSSMACK; /* KISS with SMACK / CRC16 */
+
+ } else if (strcmp(param1, "poll") == 0) {
+ /* FIXME: Some systems want polling... */
+
+ } else if (strcmp(param1, "callsign") == 0 ||
+ strcmp(param1, "alias") == 0) {
+ param1 = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+ config_STRUPPER(param1);
+ tty->ttycallsign[tncid] = strdup(param1);
+
+#ifdef PF_AX25 /* PF_AX25 exists -- highly likely a Linux system ! */
+ tty->netax25[tncid] = netax25_open(param1);
+#endif
+
+ /* Use side-effect: this defines the tty into
+ erlang accounting */
+
+ erlang_set(param1, /* Heuristic constant for max channel capa.. */ (int) ((1200.0 * 60) / 8.2));
+
+ } else if (strcmp(param1, "timeout") == 0) {
+ param1 = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+ tty->read_timeout = atol(param1);
+
+ } else if (strcmp(param1, "tncid") == 0) {
+ param1 = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+ tncid = atoi(param1);
+ if (tncid < 0 || tncid > 15) {
+ tncid = 0;
+ printf("%s:%d TNCID value not in sanity range of 0 to 15: '%s'", cf->name, cf->linenum, param1);
+ has_fault = 1;
+ }
+
+ } else if (strcmp(param1, "pollmillis") == 0) {
+ param1 = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+ tty->poll_millis = atol(param1); // milliseconds
+ if (poll_millis == 0)
+ poll_millis = tty->poll_millis;
+ if (tty->poll_millis < poll_millis)
+ poll_millis = tty->poll_millis;
+ if (poll_millis < 1 || poll_millis > 10000) {
+ has_fault = 1;
+ printf("%s:%d POLLMILLIS value not in sanity range of 1 to 10 000: '%s'", cf->name, cf->linenum, param1);
+ } else {
+ if (debug)
+ printf(" .. pollmillis %d -- polling interval\n", tty->poll_millis);
+ }
+
+#ifndef DISABLE_IGATE
+ } else if (strcmp(param1, "tnc2") == 0) {
+ tty->linetype = LINETYPE_TNC2; /* TNC2 monitor */
+
+ } else if (strcmp(param1, "dprs") == 0) {
+ tty->linetype = LINETYPE_DPRSGW;
+#endif
+
+ } else if (strcmp(param1, "initstring") == 0) {
+ int parlen;
+ param1 = str;
+ str = config_SKIPTEXT(str, &parlen);
+ str = config_SKIPSPACE(str);
+ tty->initlen[tncid] = parlen;
+ tty->initstring[tncid] = malloc(parlen);
+ memcpy(tty->initstring[tncid], param1, parlen);
+
+ if (debug)
+ printf("initstring len=%d\n",parlen);
+ } else {
+ printf("%s:%d ERROR: Unknown sub-keyword on a serial/tcp device configuration: '%s'\n",
+ cf->name, cf->linenum, param1);
+ has_fault = 1;
+ }
+ }
+ if (debug) printf("\n");
+ return has_fault;
+}
+
+
+void ttyreader_register(struct serialport *tty)
+{
+ /* Grow the array as is needed.. - this is array of pointers,
+ not array of blocks so that memory allocation does not
+ grow into way too big chunks. */
+ ttys = realloc(ttys, sizeof(void *) * (ttycount + 1));
+ ttys[ttycount++] = tty;
+}
+
+const char *ttyreader_serialcfg(struct configfile *cf, char *param1, char *str)
+{ /* serialport /dev/ttyUSB123 19200 8n1 {KISS|TNC2|AEA|..} */
+ struct serialport *tty;
+
+ /*
+ radio serial /dev/ttyUSB123 [19200 [8n1]] KISS
+ radio tcp 12.34.56.78 4001 KISS
+
+ */
+
+ if (*param1 == 0)
+ return "Bad mode keyword";
+ if (*str == 0)
+ return "Bad tty-name/param";
+
+ tty = ttyreader_new();
+ ttyreader_register(tty);
+
+ if (strcmp(param1, "serial") == 0) {
+ /* New style! */
+ free((char *) (tty->ttyname));
+
+ param1 = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ tty->ttyname = strdup(param1);
+
+ if (debug)
+ printf(".. new style serial: '%s' '%s'..\n",
+ tty->ttyname, str);
+
+ } else if (strcmp(param1, "tcp") == 0) {
+ /* New style! */
+ int len;
+ char *host, *port;
+
+ free((char *) (tty->ttyname));
+
+ host = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ port = str;
+ str = config_SKIPTEXT(str, NULL);
+ str = config_SKIPSPACE(str);
+
+ if (debug)
+ printf(".. new style tcp!: '%s' '%s' '%s'..\n",
+ host, port, str);
+
+ len = strlen(host) + strlen(port) + 8;
+
+ tty->ttyname = malloc(len);
+ sprintf((char *) (tty->ttyname), "tcp!%s!%s!", host, port);
+
+ }
+
+ if (ttyreader_parse_ttyparams( cf, tty, str))
+ return "Bad ttyparameters";
+
+ return NULL; // All OK
+}
+
diff --git a/valgrind.c b/valgrind.c
new file mode 100644
index 0000000..e23506f
--- /dev/null
+++ b/valgrind.c
@@ -0,0 +1,101 @@
+/* **************************************************************** *
+ * *
+ * APRX -- 2nd generation receive-only APRS-i-gate with *
+ * minimal requirement of esoteric facilities or *
+ * libraries of any kind beyond UNIX system libc. *
+ * *
+ * (c) Matti Aarnio - OH2MQK, 2007-2014 *
+ * *
+ * **************************************************************** */
+
+#ifdef _FOR_VALGRIND_
+#include "aprx.h"
+
+/*
+ * High-efficiency algorithms used by libc cause terrible complaints
+ * from valgrind..
+ *
+ * These naive single char at the time things don't go reading into
+ * uninitialized areas..
+ *
+ */
+
+int memcmp(const void *p1, const void *p2, size_t n) {
+ const char *s1 = p1;
+ const char *s2 = p2;
+ for( ; n > 0 && *s1 == *s2 ; ++s1, ++s2, --n ) ;
+ if (n == 0) return 0;
+ return (*s1 - *s2);
+}
+void *memcpy(void *dest, const void *src, size_t n) {
+ char *p = dest;
+ const char *s = src;
+ for ( ; n > 0; --n ) {
+ *p++ = *s++;
+ }
+ return dest;
+}
+size_t strlen(const char *p) {
+ size_t i;
+ for ( i = 0; *p != 0; ++p, ++i ) ;
+ return i;
+}
+char *strdup(const char *s) {
+ int len = strlen(s)+1;
+ char *p = malloc(len);
+ memcpy(p, s, len);
+ return p;
+}
+int strcmp(const char *s1, const char *s2) {
+ for( ; *s1 && *s2 && *s1 == *s2 ; ++s1, ++s2 ) ;
+ if (*s1 == 0 && *s2 == 0) return 0;
+ if (*s1 == 0) return -1;
+ if (*s2 == 0) return 1;
+ return (*s1 - *s2);
+}
+int strncmp(const char *s1, const char *s2, size_t n) {
+ for( ; n > 0 && *s1 && *s2 && *s1 == *s2 ; ++s1, ++s2, --n ) ;
+ if (n == 0) return 0;
+ if (*s1 == 0) return -1;
+ if (*s2 == 0) return 1;
+ return (*s1 - *s2);
+}
+char *strcpy(char *dest, const char *src) {
+ char *p = dest;
+ while (*src != 0) {
+ *p++ = *src++;
+ }
+ return dest;
+}
+char *strncpy(char *dest, const char *src, size_t n) {
+ char *p = dest;
+ for (;*src != 0 && n > 0; --n) {
+ *p++ = *src++;
+ }
+ return dest;
+}
+void *memchr(const void *s, int c, size_t n) {
+ const unsigned char *p = s;
+ c &= 0xFF;
+ for (p = s; n > 0; --n, ++p) {
+ if (*p == c) return (void*)p;
+ }
+ return NULL;
+}
+void *memrchr(const void *s, int c, size_t n) {
+ const unsigned char *p = s;
+ c &= 0xFF;
+ for (p = s+n; n > 0; --n, --p) {
+ if (*p == c) return (void*)p;
+ }
+ return NULL;
+}
+char *strchr(const char *s, int c) {
+ c &= 0xFF;
+ for (; *s != 0; ++s) {
+ if (((*s) & 0xFF) == c) return (char*)s;
+ }
+ return NULL;
+}
+
+#endif
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-hamradio/aprx.git
More information about the pkg-hamradio-commits
mailing list