diff --git a/.appveyor.yml b/.appveyor.yml index f98fb81bb..eba6f4aaa 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -37,6 +37,8 @@ skip_commits: # build matrix configuration # #---------------------------------# +image: Visual Studio 2015 + # Build Configurations: dll/static, regular/debug configuration: - dynamic @@ -49,6 +51,7 @@ environment: # common / default variables for all jobs SETUP_PATH: .ci-local:.ci BASE: SELF + EPICS_TEST_IMPRECISE_TIMING: YES matrix: - CMP: vs2019 @@ -59,7 +62,7 @@ environment: - CMP: vs2013 - CMP: vs2012 - CMP: vs2010 - - CMP: mingw + - CMP: gcc # Platform: processor architecture platform: @@ -89,17 +92,17 @@ matrix: install: - cmd: git submodule update --init --recursive - - cmd: python .ci/appveyor/do.py prepare + - cmd: python .ci/cue.py prepare build_script: - - cmd: python .ci/appveyor/do.py build + - cmd: python .ci/cue.py build test_script: - - cmd: python .ci/appveyor/do.py test + - cmd: python .ci/cue.py test on_finish: - ps: Get-ChildItem *.tap -Recurse -Force | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name } - - cmd: python .ci/appveyor/do.py build test-results -s + - cmd: python .ci/cue.py build test-results -s #---------------------------------# # debugging # diff --git a/.ci-local/travis-fixup.sh b/.ci-local/travis-fixup.sh new file mode 100755 index 000000000..7d1ea8235 --- /dev/null +++ b/.ci-local/travis-fixup.sh @@ -0,0 +1,19 @@ +#!/bin/sh +set -e -u -x + +env|grep TRAVIS + +[ "$TRAVIS_OS_NAME" = "linux" ] || exit 0 + +# Ensure there is an interface with a (correct) broadcast address +# eg. 'trusty' VMs have interface broadcast address mis-configured +# (why oh why do people insist on setting this explicitly?) + +sudo ip tuntap add dev tap42 mode tap + +sudo ip addr add 192.168.240.1/24 broadcast + dev tap42 + +sudo ip link set dev tap42 up + +# note that this device will be UP but not RUNNING +# so java will see it as not UP since java confuses UP and RUNNING diff --git a/.travis.yml b/.travis.yml index a2f16784b..c4ee7adf3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ env: global: - SETUP_PATH=.ci-local:.ci - BASE=SELF + - EPICS_TEST_IMPRECISE_TIMING=YES addons: apt: @@ -35,10 +36,13 @@ addons: update: true install: - - ./.ci/travis/prepare.sh + - ./.ci-local/travis-fixup.sh + - python .ci/cue.py prepare script: - - ./.ci/travis/build.sh + - python .ci/cue.py build + - python .ci/cue.py test + - python .ci/cue.py test-results # Define build jobs @@ -51,7 +55,7 @@ jobs: - dist: xenial - dist: bionic - env: STATIC=YES EXTRA="CMD_CXXFLAGS=-std=c++11" + env: BCFG=static EXTRA="CMD_CXXFLAGS=-std=c++11" - dist: trusty env: EXTRA="CMD_CXXFLAGS=-std=c++11" @@ -63,15 +67,13 @@ jobs: - dist: trusty compiler: clang - env: STATIC=YES + env: BCFG=static # Cross-compilations to Windows using MinGW and WINE - - env: WINE=32 TEST=NO STATIC=YES - compiler: mingw + - env: WINE=32 TEST=NO BCFG=static - - env: WINE=32 TEST=NO STATIC=NO - compiler: mingw + - env: WINE=32 TEST=NO # Cross-compilation to RTEMS diff --git a/configure/RULES_BUILD b/configure/RULES_BUILD index 77d2d32a6..54f04149f 100644 --- a/configure/RULES_BUILD +++ b/configure/RULES_BUILD @@ -133,7 +133,7 @@ ifneq (,$(filter $(T_A), $(EPICS_HOST_ARCH) $(CROSS_COMPILER_RUNTEST_ARCHS))) RUNTESTS_ENABLED = YES TESTSCRIPTS.t = $(filter %.t, $(TESTSCRIPTS)) TAPFILES.t += $(TESTSCRIPTS.t:.t=.tap) -JUNITFILES.t += $(TESTSCRIPTS.t:.t=.xml) +JUNITFILES.t += $(TESTSCRIPTS.t:.t=-results.xml) TAPFILES += $(TAPFILES.t) JUNITFILES += $(JUNITFILES.t) endif @@ -403,7 +403,7 @@ ifdef RUNTESTS_ENABLED $(PERL) $< -tap > $@ endif -$(JUNITFILES.t): %.xml: %.tap +$(JUNITFILES.t): %-results.xml: %.tap $(TAPTOJUNIT) --puretap --output $@ --input $< $* # If there's a perl test script (.plt) available, use it diff --git a/documentation/RELEASE_NOTES.md b/documentation/RELEASE_NOTES.md index 6fd80615a..e78bb6438 100644 --- a/documentation/RELEASE_NOTES.md +++ b/documentation/RELEASE_NOTES.md @@ -1329,6 +1329,15 @@ header and removed the need for dbScan.c to reach into the internals of its # Changes incorporated from the 3.15 branch +## Changes made on the 3.15 branch since 3.15.8 + +### Change to the `junitfiles` self-test build target + +The names of the generated junit xml test output files have been changed +from `.xml` to `-results.xml`, to allow better +distinction from other xml files. (I.e., for easy wildcard matching.) + + ## Changes made between 3.15.7 and 3.15.8 ### Bug fixes @@ -1352,7 +1361,6 @@ The following launchpad bugs have fixes included in this release: - [lp: 1868486](https://bugs.launchpad.net/epics-base/+bug/1868486), epicsMessageQueue lost messages - ### Improvements to the self-test build targets This release contains changes that make it possible to integrate another test @@ -1368,7 +1376,7 @@ results; previously the `-k` flag to make was needed and even that didn't always work. Continuous Integration systems are recommended to run `make tapfiles` (or if -they can read junittest output instead of TAP `make junitests`) followed by +they can read junittest output instead of TAP `make junitfiles`) followed by `make -s test-results` to display the results of the tests. If multiple CPUs are available the `-j` flag can be used to run tests in parallel, giving the maximum jobs that should be allowed so `make -j4 tapfiles` for a system with 4 CPUs say. @@ -1616,8 +1624,8 @@ cases. This fixes Some documentation has been added to the `dbdToHtml.pl` script explaining how Perl POD (Plain Old Documentation) markup can be added to `.dbd` files to generate HTML documentation for the record types. To see -these instructions, run `perl bin//dbdToHtml.pl -H` -or `perldoc bin//dbdToHtml.pl`. +these instructions, run `perl bin//dbdToHtml.pl -H` +or `perldoc bin//dbdToHtml.pl`. ### Fix problem with numeric soft events diff --git a/modules/ca/src/client/access.cpp b/modules/ca/src/client/access.cpp index 169a08bef..4e4669eec 100644 --- a/modules/ca/src/client/access.cpp +++ b/modules/ca/src/client/access.cpp @@ -603,7 +603,7 @@ void epicsStdCall ca_signal_formated ( long ca_status, const char *pfilenm, } else { fprintf ( stderr, "CA exception in thread w/o CA ctx: status=%s file=%s line=%d: \n", - ca_message ( ca_status ), pfilenm, lineno ); + ca_message ( ca_status ), pfilenm ? pfilenm : "", lineno ); if ( pFormat ) { vfprintf ( stderr, pFormat, theArgs ); } diff --git a/modules/ca/src/perl/Makefile b/modules/ca/src/perl/Makefile index 65619e93e..532780d4c 100644 --- a/modules/ca/src/perl/Makefile +++ b/modules/ca/src/perl/Makefile @@ -13,11 +13,11 @@ ifdef T_A PERL_ARCHNAME = $(shell $(PERL) ../perlConfig.pl archname) PERL_ARCHPATH := $(PERL_VERSION)/$(PERL_ARCHNAME) - PERL_ARCHLIB := $(shell $(PERL) ../perlConfig.pl archlib) + PERL_ARCHLIB := $(shell $(PERL) ../perlConfig.pl archlibexp) PERL_h = $(PERL_ARCHLIB)/CORE/perl.h - EXTUTILS := $(shell $(PERL) ../perlConfig.pl privlib)/ExtUtils - PERLBIN := $(shell $(PERL) ../perlConfig.pl bin) + EXTUTILS := $(shell $(PERL) ../perlConfig.pl privlibexp)/ExtUtils + PERLBIN := $(shell $(PERL) ../perlConfig.pl binexp) XSUBPP := $(firstword $(wildcard $(PERLBIN)/xsubpp $(EXTUTILS)/xsubpp)) # Special settings for Darwin: diff --git a/modules/ca/src/perl/capr.pl b/modules/ca/src/perl/capr.pl index 21e56ecd9..87a058c6f 100644 --- a/modules/ca/src/perl/capr.pl +++ b/modules/ca/src/perl/capr.pl @@ -35,9 +35,11 @@ my %fieldType = ( DBF_DOUBLE => 'DBF_FLOAT', DBF_FLOAT => 'DBF_FLOAT', DBF_LONG => 'DBF_LONG', + DBF_INT64 => 'DBF_FLOAT', DBF_SHORT => 'DBF_LONG', DBF_ULONG => 'DBF_LONG', DBF_USHORT => 'DBF_LONG', + DBF_UINT64 => 'DBF_FLOAT', DBF_DEVICE => 'DBF_STRING', DBF_ENUM => 'DBF_STRING', DBF_FWDLINK => 'DBF_STRING', @@ -86,7 +88,7 @@ if (@ARGV) { printRecord($_, @ARGV); } else { if (m/^ \s* ([]+:;<>0-9A-Za-z[-]+) (?:\. \w+)? \s* , \s* (\d+) \s* $/x) { - # Recognizes ",n" as an interest leve, drops any ".FIELD" part + # Recognizes ",n" as an interest level, drops any ".FIELD" part printRecord($1, $2); } else { # Drop any ".FIELD" part @@ -235,8 +237,9 @@ sub printField { $outStr = sprintf('%-5s %.8f', $field, $fieldData); } elsif ( $dataType eq 'DBF_CHAR' ) { $outStr = sprintf('%-5s %d', $field, ord($fieldData)); - }else { - # DBF_LONG, DBF_SHORT, DBF_UCHAR, DBF_ULONG, DBF_USHORT + } else { + # DBF_INT64, DBF_LONG, DBF_SHORT, + # DBF_UINT64, DBF_ULONG, DBF_USHORT, DBF_UCHAR, $outStr = sprintf('%-5s %d', $field, $fieldData); } @@ -270,17 +273,18 @@ sub printField { sub caget { my @chans = map { CA->new($_); } @_; - #clear results; + #clear any previous results; %callback_data = (); %timed_out = (); eval { CA->pend_io($opt_w); }; if ($@) { if ($@ =~ m/^ECA_TIMEOUT/) { - my $err = (@chans > 1) ? 'some PV(s)' : '"' . $chans[0]->name . '"'; + my $name = $chans[0]->name; + my $err = (@chans > 1) ? 'some fields' : "'$name'"; print "Channel connect timed out: $err not found.\n"; foreach my $chan (@chans) { - $timed_out{$chan->name} = $chan->is_connected; + $timed_out{$chan->name} = !$chan->is_connected; } @chans = grep { $_->is_connected } @chans; } else { @@ -289,14 +293,12 @@ sub caget { } map { - my $type; - $type = $_->field_type; - $_->get_callback(\&caget_callback, $type); + $_->get_callback(\&caget_callback, $_->field_type); } @chans; - my $fields_read = @chans; - $callback_incomplete = @chans; - CA->pend_event(0.1) while $callback_incomplete; + my $fields_read = $callback_incomplete = @chans; + CA->pend_event(0.1) + while $callback_incomplete; return $fields_read; } @@ -325,9 +327,10 @@ sub printRecord { my @bases = (); #bases, from parser foreach my $field (sort keys %{$record{$recType}}) { # Skip DTYP field if this rec type doesn't have device support defined - if ($field eq 'DTYP' && !(exists($device{$recType}))) { next; } + next if $field eq 'DTYP' && !exists($device{$recType}); my ($fType, $fInterest, $base) = getFieldParams($recType, $field); + # FIXME: Support waveform.VAL fields etc. unless( $fType eq 'DBF_NOACCESS' ) { if ($interest >= $fInterest ) { my $fToGet = "$name.$field"; @@ -346,15 +349,10 @@ sub printRecord { my $field = $fields_pr[$i]; my $fToGet = $readlist[$i]; my ($fType, $data, $base); - if ($timed_out{$fToGet}) { - $fType = $fieldType{DBF_STRING}; - $data = ''; - } - else { - $fType = $ftypes[$i]; - $base = $bases[$i]; - $data = $callback_data{$fToGet}; - } + next if $timed_out{$fToGet}; + $fType = $ftypes[$i]; + $base = $bases[$i]; + $data = $callback_data{$fToGet}; $col = printField($field, $data, $fType, $base, $col); } print("\n"); # Final newline @@ -387,8 +385,8 @@ sub printRecordList { if (exists($record{$type}) ) { print("Record type - $type\n"); foreach my $fkey (sort keys %{$record{$type}}) { - printf('%-4s', $fkey); - printf(" interest = $record{$type}{$fkey}[$iIdx]"); + printf('%-8s', $fkey); + printf(" interest = $record{$type}{$fkey}[$iIdx]"); printf(" type = %-12s ",$record{$type}{$fkey}[$tIdx]); print (" base = $record{$type}{$fkey}[$bIdx]\n"); } diff --git a/modules/ca/src/tools/cainfo.c b/modules/ca/src/tools/cainfo.c index 13696de34..d65204e14 100644 --- a/modules/ca/src/tools/cainfo.c +++ b/modules/ca/src/tools/cainfo.c @@ -157,7 +157,7 @@ int main (int argc, char *argv[]) } break; case 's': /* ca_client_status interest level */ - if (sscanf(optarg,"%du", &statLevel) != 1) + if (sscanf(optarg,"%u", &statLevel) != 1) { fprintf(stderr, "'%s' is not a valid interest level " "- ignored. ('cainfo -h' for help.)\n", optarg); diff --git a/modules/ca/src/tools/camonitor.c b/modules/ca/src/tools/camonitor.c index e83883702..682a9c53a 100644 --- a/modules/ca/src/tools/camonitor.c +++ b/modules/ca/src/tools/camonitor.c @@ -258,7 +258,7 @@ int main (int argc, char *argv[]) } break; case '#': /* Array count */ - if (sscanf(optarg,"%ld", &reqElems) != 1) + if (sscanf(optarg,"%lu", &reqElems) != 1) { fprintf(stderr, "'%s' is not a valid array element count " "- ignored. ('camonitor -h' for help.)\n", optarg); diff --git a/modules/ca/src/tools/caput.c b/modules/ca/src/tools/caput.c index 0cd923e72..c44bd22cb 100644 --- a/modules/ca/src/tools/caput.c +++ b/modules/ca/src/tools/caput.c @@ -437,6 +437,7 @@ int main (int argc, char *argv[]) dbuf = calloc (count, sizeof(double)); if(!sbuf || !dbuf) { fprintf(stderr, "Memory allocation failed\n"); + free(sbuf); free(dbuf); return 1; } @@ -450,6 +451,7 @@ int main (int argc, char *argv[]) result = ca_pend_io(caTimeout); if (result == ECA_TIMEOUT) { fprintf(stderr, "Read operation timed out: ENUM data was not read.\n"); + free(sbuf); free(dbuf); return 1; } @@ -460,6 +462,7 @@ int main (int argc, char *argv[]) if (*(argv+optind+i) == pend) { /* Conversion didn't work */ fprintf(stderr, "Enum index value '%s' is not a number.\n", *(argv+optind+i)); + free(sbuf); free(dbuf); return 1; } if (dbuf[i] >= bufGrEnum.no_str) { @@ -486,6 +489,7 @@ int main (int argc, char *argv[]) dbuf[i] = epicsStrtod(sbuf[i], &pend); if (sbuf[i] == pend || enumAsString) { fprintf(stderr, "Enum string value '%s' invalid.\n", sbuf[i]); + free(sbuf); free(dbuf); return 1; } if (dbuf[i] >= bufGrEnum.no_str) { @@ -503,6 +507,7 @@ int main (int argc, char *argv[]) ebuf = calloc(len, sizeof(char)); if(!ebuf) { fprintf(stderr, "Memory allocation failed\n"); + free(sbuf); free(dbuf); free(ebuf); return 1; } count = epicsStrnRawFromEscaped(ebuf, len, cbuf, len-1) + 1; @@ -537,12 +542,14 @@ int main (int argc, char *argv[]) } if (result != ECA_NORMAL) { fprintf(stderr, "Error from put operation: %s\n", ca_message(result)); + free(sbuf); free(dbuf); free(ebuf); return 1; } result = ca_pend_io(caTimeout); if (result == ECA_TIMEOUT) { fprintf(stderr, "Write operation timed out: Data was not written.\n"); + free(sbuf); free(dbuf); free(ebuf); return 1; } if (request == callback) { /* Also wait for callbacks */ @@ -556,6 +563,7 @@ int main (int argc, char *argv[]) if (result != ECA_NORMAL) { fprintf(stderr, "Error occured writing data: %s\n", ca_message(result)); + free(sbuf); free(dbuf); free(ebuf); return 1; } @@ -567,6 +575,7 @@ int main (int argc, char *argv[]) /* Shut down Channel Access */ ca_context_destroy(); + free(sbuf); free(dbuf); free(ebuf); return result; } diff --git a/modules/database/src/ioc/db/dbChannel.c b/modules/database/src/ioc/db/dbChannel.c index bc3c8e421..ad03ffdf3 100644 --- a/modules/database/src/ioc/db/dbChannel.c +++ b/modules/database/src/ioc/db/dbChannel.c @@ -603,6 +603,10 @@ long dbChannelOpen(dbChannel *chan) probe.no_elements = dbChannelElements(chan); probe.field_size = dbChannelFieldSize(chan); probe.sevr = NO_ALARM; + probe.stat = NO_ALARM; + probe.time.secPastEpoch = 0; + probe.time.nsec = 0; + p = probe; /* diff --git a/modules/database/src/ioc/db/recGbl.c b/modules/database/src/ioc/db/recGbl.c index 93ac8aa6f..c54dfe515 100644 --- a/modules/database/src/ioc/db/recGbl.c +++ b/modules/database/src/ioc/db/recGbl.c @@ -69,7 +69,7 @@ void recGblDbaddrError(long status, const struct dbAddr *paddr, errPrintf(status,0,0, "PV: %s.%s " "error detected in routine: %s\n", - (paddr ? precord->name : "Unknown"), + (precord ? precord->name : "Unknown"), (pdbFldDes ? pdbFldDes->name : ""), (pmessage ? pmessage : "Unknown")); return; @@ -106,7 +106,7 @@ void recGblRecSupError(long status, const struct dbAddr *paddr, " %s\n", (psupport_name ? psupport_name : "Unknown"), (pdbRecordType ? pdbRecordType->name : "Unknown"), - (paddr ? precord->name : "Unknown"), + (precord ? precord->name : "Unknown"), (pdbFldDes ? pdbFldDes->name : ""), (pmessage ? pmessage : "")); return; diff --git a/modules/database/src/ioc/dbStatic/dbLexRoutines.c b/modules/database/src/ioc/dbStatic/dbLexRoutines.c index 91b3acc68..531943d08 100644 --- a/modules/database/src/ioc/dbStatic/dbLexRoutines.c +++ b/modules/database/src/ioc/dbStatic/dbLexRoutines.c @@ -136,12 +136,14 @@ static void allocTemp(void *pvoid) static void *popFirstTemp(void) { tempListNode *ptempListNode; - void *ptemp; + void *ptemp = NULL; ptempListNode = (tempListNode *)ellFirst(&tempList); - ptemp = ptempListNode->item; - ellDelete(&tempList,(ELLNODE *)ptempListNode); - freeListFree(freeListPvt,ptempListNode); + if(ptempListNode) { + ptemp = ptempListNode->item; + ellDelete(&tempList,(ELLNODE *)ptempListNode); + freeListFree(freeListPvt,ptempListNode); + } return(ptemp); } @@ -477,12 +479,16 @@ static void dbMenuBody(void) return; } pnewMenu = (dbMenu *)popFirstTemp(); + if(!pnewMenu) + return; pnewMenu->nChoice = nChoice = ellCount(&tempList)/2; pnewMenu->papChoiceName = dbCalloc(pnewMenu->nChoice,sizeof(char *)); pnewMenu->papChoiceValue = dbCalloc(pnewMenu->nChoice,sizeof(char *)); for(i=0; ipapChoiceName[i] = (char *)popFirstTemp(); pnewMenu->papChoiceValue[i] = (char *)popFirstTemp(); + if(!pnewMenu->papChoiceName[i] || !pnewMenu->papChoiceValue[i]) + return; } if(ellCount(&tempList)) yyerrorAbort("dbMenuBody: tempList not empty"); /* Add menu in sorted order */ @@ -703,6 +709,8 @@ static void dbRecordtypeBody(void) return; } pdbRecordType= (dbRecordType *)popFirstTemp(); + if(!pdbRecordType) + return; pdbRecordType->no_fields = no_fields = ellCount(&tempList); pdbRecordType->papFldDes = dbCalloc(no_fields,sizeof(dbFldDes *)); pdbRecordType->papsortFldName = dbCalloc(no_fields,sizeof(char *)); @@ -710,6 +718,8 @@ static void dbRecordtypeBody(void) no_prompt = no_links = 0; for(i=0; ipdbRecordType = pdbRecordType; pdbFldDes->indRecordType = i; pdbRecordType->papFldDes[i] = pdbFldDes; @@ -974,6 +984,8 @@ static void dbBreakBody(void) return; } pnewbrkTable = (brkTable *)popFirstTemp(); + if(!pnewbrkTable) + return; number = ellCount(&tempList); if (number % 2) { yyerrorAbort("breaktable: Raw value missing"); @@ -990,10 +1002,14 @@ static void dbBreakBody(void) char *str; str = (char *)popFirstTemp(); + if(!str) + return; (void) epicsScanDouble(str, &paBrkInt[i].raw); free(str); str = (char *)popFirstTemp(); + if(!str) + return; (void) epicsScanDouble(str, &paBrkInt[i].eng); free(str); } @@ -1034,22 +1050,49 @@ static void dbBreakBody(void) } pgphentry->userPvt = pnewbrkTable; } - + +static +int dbRecordNameValidate(const char *name) +{ + size_t i=0u; + const char *pos = name; + + if (!*name) { + yyerrorAbort("Error: Record/Alias name can't be empty"); + return 1; + } + + for(; *pos; i++, pos++) { + char c = *pos; + if(i==0) { + /* first character restrictions */ + if(c=='-' || c=='+' || c=='[' || c=='{') { + errlogPrintf("Warning: Record/Alias name '%s' should not begin with '%c'\n", name, c); + } + } + /* any character restrictions */ + if(c < ' ') { + errlogPrintf("Warning: Record/Alias name '%s' should not contain non-printable 0x%02u\n", + name, (unsigned)c); + + } else if(c==' ' || c=='\t' || c=='"' || c=='\'' || c=='.' || c=='$') { + epicsPrintf("Error: Bad character '%c' in Record/Alias name \"%s\"\n", + c, name); + yyerrorAbort(NULL); + return 1; + } + } + + return 0; +} + static void dbRecordHead(char *recordType, char *name, int visible) { - char *badch; DBENTRY *pdbentry; long status; - if (!*name) { - yyerrorAbort("dbRecordHead: Record name can't be empty"); + if(dbRecordNameValidate(name)) return; - } - badch = strpbrk(name, " \"'.$"); - if (badch) { - epicsPrintf("Bad character '%c' in record name \"%s\"\n", - *badch, name); - } pdbentry = dbAllocEntry(pdbbase); if (ellCount(&tempList)) @@ -1180,10 +1223,9 @@ static void dbRecordAlias(char *name) tempListNode *ptempListNode; long status; - if (!*name) { - yyerrorAbort("dbRecordAlias: Alias name can't be empty"); + if(dbRecordNameValidate(name)) return; - } + if (duplicate) return; ptempListNode = (tempListNode *)ellFirst(&tempList); pdbentry = ptempListNode->item; @@ -1201,10 +1243,9 @@ static void dbAlias(char *name, char *alias) DBENTRY dbEntry; DBENTRY *pdbEntry = &dbEntry; - if (!*alias) { - yyerrorAbort("dbAlias: Alias name can't be empty"); + if(dbRecordNameValidate(alias)) return; - } + dbInitEntry(pdbbase, pdbEntry); if (dbFindRecord(pdbEntry, name)) { epicsPrintf("Alias \"%s\" refers to unknown record \"%s\"\n", diff --git a/modules/libcom/src/flex/misc.c b/modules/libcom/src/flex/misc.c index ffcc6de9a..a594d1663 100644 --- a/modules/libcom/src/flex/misc.c +++ b/modules/libcom/src/flex/misc.c @@ -438,7 +438,7 @@ int htoi(unsigned char *str) { int result; - (void) sscanf( (char *) str, "%x", &result ); + (void) sscanf( (char *) str, "%x", (unsigned *) &result ); return ( result ); } @@ -653,7 +653,7 @@ int otoi(Char *str) { int result; - (void) sscanf( (char *) str, "%o", &result ); + (void) sscanf( (char *) str, "%o", (unsigned *) &result ); return ( result ); } diff --git a/modules/libcom/src/osi/os/RTEMS/osdMessageQueue.c b/modules/libcom/src/osi/os/RTEMS/osdMessageQueue.c deleted file mode 100644 index 29398e205..000000000 --- a/modules/libcom/src/osi/os/RTEMS/osdMessageQueue.c +++ /dev/null @@ -1,250 +0,0 @@ -/*************************************************************************\ -* Copyright (c) 2002 The University of Chicago, as Operator of Argonne -* National Laboratory. -* Copyright (c) 2002 The Regents of the University of California, as -* Operator of Los Alamos National Laboratory. -* EPICS BASE Versions 3.13.7 -* and higher are distributed subject to a Software License Agreement found -* in file LICENSE that is included with this distribution. -\*************************************************************************/ -/* - * Author W. Eric Norum - * norume@aps.anl.gov - * 630 252 4793 - */ - -/* - * We want to access information which is - * normally hidden from application programs. - */ -#define __RTEMS_VIOLATE_KERNEL_VISIBILITY__ 1 - -#include -#include -#include -#include -#include -#include -#include "epicsMessageQueue.h" -#include "errlog.h" - -LIBCOM_API epicsMessageQueueId epicsStdCall -epicsMessageQueueCreate(unsigned int capacity, unsigned int maximumMessageSize) -{ - rtems_status_code sc; - epicsMessageQueueId id = calloc(1, sizeof(*id)); - rtems_interrupt_level level; - static char c1 = 'a'; - static char c2 = 'a'; - static char c3 = 'a'; - - if(!id) - return NULL; - - sc = rtems_message_queue_create (rtems_build_name ('Q', c3, c2, c1), - capacity, - maximumMessageSize, - RTEMS_FIFO|RTEMS_LOCAL, - &id->id); - if (sc != RTEMS_SUCCESSFUL) { - free(id); - errlogPrintf ("Can't create message queue: %s\n", rtems_status_text (sc)); - return NULL; - } - id->maxSize = maximumMessageSize; - id->localBuf = NULL; - rtems_interrupt_disable (level); - if (c1 == 'z') { - if (c2 == 'z') { - if (c3 == 'z') { - c3 = 'a'; - } - else { - c3++; - } - c2 = 'a'; - } - else { - c2++; - } - c1 = 'a'; - } - else { - c1++; - } - rtems_interrupt_enable (level); - return id; -} - -static rtems_status_code rtems_message_queue_send_timeout( - rtems_id id, - void *buffer, - uint32_t size, - rtems_interval timeout) -{ - Message_queue_Control *the_message_queue; - Objects_Locations location; - CORE_message_queue_Status msg_status; - - the_message_queue = _Message_queue_Get( id, &location ); - switch ( location ) - { - case OBJECTS_ERROR: - return RTEMS_INVALID_ID; - - case OBJECTS_LOCAL: - msg_status = _CORE_message_queue_Send( - &the_message_queue->message_queue, - buffer, - size, - id, - NULL, - 1, - timeout - ); - - _Thread_Enable_dispatch(); - - /* - * If we had to block, then this is where the task returns - * after it wakes up. The returned status is correct for - * non-blocking operations but if we blocked, then we need - * to look at the status in our TCB. - */ - - if ( msg_status == CORE_MESSAGE_QUEUE_STATUS_UNSATISFIED_WAIT ) - msg_status = _Thread_Executing->Wait.return_code; - return _Message_queue_Translate_core_message_queue_return_code( msg_status ); - } - return RTEMS_INTERNAL_ERROR; /* unreached - only to remove warnings */ -} - -LIBCOM_API int epicsStdCall epicsMessageQueueSend( - epicsMessageQueueId id, - void *message, - unsigned int messageSize) -{ - if (rtems_message_queue_send_timeout(id->id, message, messageSize, RTEMS_NO_TIMEOUT) == RTEMS_SUCCESSFUL) - return 0; - else - return -1; -} - -LIBCOM_API int epicsStdCall epicsMessageQueueSendWithTimeout( - epicsMessageQueueId id, - void *message, - unsigned int messageSize, - double timeout) -{ - rtems_interval delay; - extern double rtemsTicksPerSecond_double; - - /* - * Convert time to ticks - */ - if (timeout <= 0.0) - return epicsMessageQueueTrySend(id, message, messageSize); - delay = (int)(timeout * rtemsTicksPerSecond_double); - if (delay == 0) - delay++; - if (rtems_message_queue_send_timeout(id->id, message, messageSize, delay) == RTEMS_SUCCESSFUL) - return 0; - else - return -1; -} - -static int receiveMessage( - epicsMessageQueueId id, - void *buffer, - uint32_t size, - uint32_t wait, - rtems_interval delay) -{ - size_t rsize; - rtems_status_code sc; - - if (size < id->maxSize) { - if (id->localBuf == NULL) { - id->localBuf = malloc(id->maxSize); - if (id->localBuf == NULL) - return -1; - } - rsize = receiveMessage(id, id->localBuf, id->maxSize, wait, delay); - if (rsize > size) - return -1; - memcpy(buffer, id->localBuf, rsize); - } - else { - sc = rtems_message_queue_receive(id->id, buffer, &rsize, wait, delay); - if (sc != RTEMS_SUCCESSFUL) - return -1; - } - return rsize; -} - -LIBCOM_API int epicsStdCall epicsMessageQueueTryReceive( - epicsMessageQueueId id, - void *message, - unsigned int size) -{ - return receiveMessage(id, message, size, RTEMS_NO_WAIT, 0); -} - -LIBCOM_API int epicsStdCall epicsMessageQueueReceive( - epicsMessageQueueId id, - void *message, - unsigned int size) -{ - return receiveMessage(id, message, size, RTEMS_WAIT, RTEMS_NO_TIMEOUT); -} - -LIBCOM_API int epicsStdCall epicsMessageQueueReceiveWithTimeout( - epicsMessageQueueId id, - void *message, - unsigned int size, - double timeout) -{ - rtems_interval delay; - uint32_t wait; - extern double rtemsTicksPerSecond_double; - - /* - * Convert time to ticks - */ - if (timeout <= 0.0) { - wait = RTEMS_NO_WAIT; - delay = 0; - } - else { - wait = RTEMS_WAIT; - delay = (int)(timeout * rtemsTicksPerSecond_double); - if (delay == 0) - delay++; - } - return receiveMessage(id, message, size, wait, delay); -} - -LIBCOM_API int epicsStdCall epicsMessageQueuePending( - epicsMessageQueueId id) -{ - uint32_t count; - rtems_status_code sc; - - sc = rtems_message_queue_get_number_pending(id->id, &count); - if (sc != RTEMS_SUCCESSFUL) { - errlogPrintf("Message queue %x get number pending failed: %s\n", - (unsigned int)id, - rtems_status_text(sc)); - return -1; - } - return count; -} - -LIBCOM_API void epicsStdCall epicsMessageQueueShow( - epicsMessageQueueId id, - int level) -{ - int pending = epicsMessageQueuePending(id); - if (pending >= 0) - printf ("Message queue %lx -- Pending: %d\n", (unsigned long)id, pending); -} diff --git a/modules/libcom/src/osi/os/RTEMS/osdMessageQueue.h b/modules/libcom/src/osi/os/RTEMS/osdMessageQueue.h deleted file mode 100644 index b3c4e8897..000000000 --- a/modules/libcom/src/osi/os/RTEMS/osdMessageQueue.h +++ /dev/null @@ -1,29 +0,0 @@ -/*************************************************************************\ -* Copyright (c) 2002 The University of Chicago, as Operator of Argonne -* National Laboratory. -* Copyright (c) 2002 The Regents of the University of California, as -* Operator of Los Alamos National Laboratory. -* EPICS BASE Versions 3.13.7 -* and higher are distributed subject to a Software License Agreement found -* in file LICENSE that is included with this distribution. -\*************************************************************************/ -/* - * Author W. Eric Norum - * norume@aps.anl.gov - * 630 252 4793 - */ - -/* - * Very thin shims around RTEMS routines - */ -#include - -struct epicsMessageQueueOSD { - rtems_id id; - unsigned int maxSize; - void *localBuf; - -}; -#define epicsMessageQueueDestroy(q) (rtems_message_queue_delete((q)->id)) - -#define epicsMessageQueueTrySend(q,m,l) (rtems_message_queue_send((q)->id, (m), (l)) == RTEMS_SUCCESSFUL ? 0 : -1) diff --git a/modules/libcom/src/osi/os/default/osdNetIntf.c b/modules/libcom/src/osi/os/default/osdNetIntf.c index 25bc64db7..105908577 100644 --- a/modules/libcom/src/osi/os/default/osdNetIntf.c +++ b/modules/libcom/src/osi/os/default/osdNetIntf.c @@ -211,7 +211,7 @@ LIBCOM_API void epicsStdCall osiSockDiscoverBroadcastAddresses pNewNode->addr.sa = pIfreqList->ifr_broadaddr; ifDepenDebugPrintf ( ( "found broadcast addr = %x\n", ntohl ( baddr.ia.sin_addr.s_addr ) ) ); } else { - ifDepenDebugPrintf ( ( "Ignoring broadcast addr = \n", ntohl ( baddr.ia.sin_addr.s_addr ) ) ); + ifDepenDebugPrintf ( ( "Ignoring broadcast addr = %x\n", ntohl ( baddr.ia.sin_addr.s_addr ) ) ); free ( pNewNode ); continue; } diff --git a/modules/libcom/src/osi/os/default/osdThreadHooks.c b/modules/libcom/src/osi/os/default/osdThreadHooks.c index 924c1a20e..23f39beb6 100644 --- a/modules/libcom/src/osi/os/default/osdThreadHooks.c +++ b/modules/libcom/src/osi/os/default/osdThreadHooks.c @@ -71,6 +71,7 @@ LIBCOM_API int epicsThreadHookAdd(EPICS_THREAD_HOOK_ROUTINE hook) return 0; } fprintf(stderr, "epicsThreadHookAdd: Locking problem\n"); + free(pHook); return -1; } diff --git a/modules/libcom/src/osi/os/vxWorks/osdThread.c b/modules/libcom/src/osi/os/vxWorks/osdThread.c index efa6f289e..37ee47ef2 100644 --- a/modules/libcom/src/osi/os/vxWorks/osdThread.c +++ b/modules/libcom/src/osi/os/vxWorks/osdThread.c @@ -473,8 +473,9 @@ LIBCOM_API void epicsThreadMap ( EPICS_THREAD_HOOK_ROUTINE func ) while (noTasks == 0) { noTasks = taskIdListGet(taskIdList, taskIdListSize); if (noTasks == taskIdListSize) { - taskIdList = realloc(taskIdList, (taskIdListSize+ID_LIST_CHUNK)*sizeof(int)); - assert(taskIdList); + int *newlist = realloc(taskIdList, (taskIdListSize+ID_LIST_CHUNK)*sizeof(int)); + assert(newlist); + taskIdList = newlist; taskIdListSize += ID_LIST_CHUNK; noTasks = 0; } diff --git a/modules/libcom/test/epicsMessageQueueTest.cpp b/modules/libcom/test/epicsMessageQueueTest.cpp index d46a3b9e5..340ae6b58 100644 --- a/modules/libcom/test/epicsMessageQueueTest.cpp +++ b/modules/libcom/test/epicsMessageQueueTest.cpp @@ -25,12 +25,9 @@ static const char *msg1 = "1234567890This is a very long message."; static volatile int sendExit = 0; static volatile int recvExit = 0; -static epicsEventId finished; -static unsigned int mediumStack; #define SLEEPY_TESTS 500 static int numSent, numReceived; -static epicsEventId complete; /* * In Numerical Recipes in C: The Art of Scientific Computing (William H. @@ -116,7 +113,6 @@ receiver(void *arg) if (!testOk1(errors == 0)) testDiag("Error count was %d", errors); testDiag("%s exiting", myName); - epicsEventSignal(finished); } extern "C" void @@ -133,15 +129,18 @@ fastReceiver(void *arg) } } recvExit = 0; - epicsEventSignal(complete); } void sleepySender(double delay) { + epicsThreadOpts opts = {epicsThreadPriorityMedium, epicsThreadStackMedium, 1}; + epicsThreadId rxThread; + testDiag("sleepySender: sending every %.3f seconds", delay); epicsMessageQueue q(4, 20); - epicsThreadCreate("Fast Receiver", epicsThreadPriorityMedium, - mediumStack, fastReceiver, &q); + rxThread = epicsThreadCreateOpt("Fast Receiver", fastReceiver, &q, &opts); + if (!rxThread) + testAbort("Task create failed"); numSent = 0; for (int i = 0 ; i < SLEEPY_TESTS ; i++) { @@ -159,7 +158,7 @@ void sleepySender(double delay) recvExit = 1; while (q.send((void *)msg1, 4) != 0) epicsThreadSleep(0.01); - epicsEventMustWait(complete); + epicsThreadMustJoin(rxThread); } extern "C" void @@ -179,11 +178,14 @@ fastSender(void *arg) } } sendExit = 0; - epicsEventSignal(complete); } void sleepyReceiver(double delay) { + epicsThreadOpts opts = {epicsThreadPriorityMedium, + epicsThreadStackMedium, 1}; + epicsThreadId txThread; + testDiag("sleepyReceiver: acquiring every %.3f seconds", delay); epicsMessageQueue q(4, 20); @@ -192,8 +194,9 @@ void sleepyReceiver(double delay) q.send((void *)msg1, 4); } - epicsThreadCreate("Fast Sender", epicsThreadPriorityMedium, - mediumStack, fastSender, &q); + txThread = epicsThreadCreateOpt("Fast Sender", fastSender, &q, &opts); + if (!txThread) + testAbort("Task create failed"); epicsThreadSleep(0.5); char cbuf[80]; @@ -208,21 +211,15 @@ void sleepyReceiver(double delay) epicsThreadSleep(delay); } -#ifdef __rtems__ - testTodoBegin("RTEMS failure expected"); -#endif testOk(numSent == SLEEPY_TESTS, "Sent %d (should be %d)", numSent, SLEEPY_TESTS); -#ifdef __rtems__ - testTodoEnd(); -#endif testOk(numReceived == SLEEPY_TESTS, "Received %d (should be %d)", numReceived, SLEEPY_TESTS); sendExit = 1; while (q.receive(cbuf, sizeof cbuf) <= 0) epicsThreadSleep(0.01); - epicsEventMustWait(complete); + epicsThreadMustJoin(txThread); } extern "C" void @@ -242,130 +239,136 @@ sender(void *arg) testDiag("%s exiting, sent %d messages", epicsThreadGetNameSelf(), i); } +#define NUM_SENDERS 4 extern "C" void messageQueueTest(void *parm) { epicsThreadId myThreadId = epicsThreadGetIdSelf(); + epicsThreadId rxThread; + epicsThreadId senderId[NUM_SENDERS]; + epicsThreadOpts opts = {epicsThreadPriorityMedium, + epicsThreadStackMedium, 1}; + unsigned int i; char cbuf[80]; int len; int pass; int want; - epicsMessageQueue *q1 = new epicsMessageQueue(4, 20); + epicsMessageQueue q1(4, 20); testDiag("Simple single-thread tests:"); - i = 0; - testOk1(q1->pending() == 0); - while (q1->trySend((void *)msg1, i ) == 0) { - i++; - testOk(q1->pending() == i, "q1->pending() == %d", i); + testOk1(q1.pending() == 0); + for (i = 0; i < 4;) { + int ret = q1.trySend((void *)msg1, i++); + testOk(ret == 0, "trySend succeeded (%d == 0)", ret); + testOk(q1.pending() == i, "loop: q1.pending() == %d", i); } - testOk1(q1->pending() == 4); + testOk1(q1.pending() == 4); want = 0; - len = q1->receive(cbuf, sizeof cbuf); - testOk1(q1->pending() == 3); + len = q1.receive(cbuf, sizeof cbuf); + testOk1(q1.pending() == 3); if (!testOk1((len == want) && (strncmp(msg1, cbuf, len) == 0))) testDiag("wanted:%d '%.*s' got:%d '%.*s'", want, want, msg1, len, len, cbuf); want++; - len = q1->receive(cbuf, sizeof cbuf); - testOk1(q1->pending() == 2); + len = q1.receive(cbuf, sizeof cbuf); + testOk1(q1.pending() == 2); if (!testOk1((len == want) && (strncmp(msg1, cbuf, len) == 0))) testDiag("wanted:%d '%.*s' got:%d '%.*s'", want, want, msg1, len, len, cbuf); - q1->trySend((void *)msg1, i++); + q1.trySend((void *)msg1, i++); want++; - len = q1->receive(cbuf, sizeof cbuf); - testOk1(q1->pending() == 2); + len = q1.receive(cbuf, sizeof cbuf); + testOk1(q1.pending() == 2); if (!testOk1((len == want) && (strncmp(msg1, cbuf, len) == 0))) testDiag("wanted:%d '%.*s' got:%d '%.*s'", want, want, msg1, len, len, cbuf); - q1->trySend((void *)msg1, i++); - testOk1(q1->pending() == 3); + q1.trySend((void *)msg1, i++); + testOk1(q1.pending() == 3); i = 3; - while ((len = q1->receive(cbuf, sizeof cbuf, 1.0)) >= 0) { + while ((len = q1.receive(cbuf, sizeof cbuf, 1.0)) >= 0) { --i; - testOk(q1->pending() == i, "q1->pending() == %d", i); + testOk(q1.pending() == i, "loop: q1.pending() == %d", i); want++; if (!testOk1((len == want) & (strncmp(msg1, cbuf, len) == 0))) testDiag("wanted:%d '%.*s' got:%d '%.*s'", want, want, msg1, len, len, cbuf); } - testOk1(q1->pending() == 0); + testOk1(q1.pending() == 0); testDiag("Test sender timeout:"); i = 0; - testOk1(q1->pending() == 0); - while (q1->send((void *)msg1, i, 1.0 ) == 0) { + testOk1(q1.pending() == 0); + while (q1.send((void *)msg1, i, 1.0 ) == 0) { i++; - testOk(q1->pending() == i, "q1->pending() == %d", i); + testOk(q1.pending() == i, "loop: q1.pending() == %d", i); } - testOk1(q1->pending() == 4); + testOk1(q1.pending() == 4); want = 0; - len = q1->receive(cbuf, sizeof cbuf); - testOk1(q1->pending() == 3); + len = q1.receive(cbuf, sizeof cbuf); + testOk1(q1.pending() == 3); if (!testOk1((len == want) && (strncmp(msg1, cbuf, len) == 0))) testDiag("wanted:%d '%.*s' got:%d '%.*s'", want, want, msg1, len, len, cbuf); want++; - len = q1->receive(cbuf, sizeof cbuf); - testOk1(q1->pending() == 2); + len = q1.receive(cbuf, sizeof cbuf); + testOk1(q1.pending() == 2); if (!testOk1((len == want) && (strncmp(msg1, cbuf, len) == 0))) testDiag("wanted:%d '%.*s' got:%d '%.*s'", want, want, msg1, len, len, cbuf); - q1->send((void *)msg1, i++, 1.0); + q1.send((void *)msg1, i++, 1.0); want++; - len = q1->receive(cbuf, sizeof cbuf); - testOk1(q1->pending() == 2); + len = q1.receive(cbuf, sizeof cbuf); + testOk1(q1.pending() == 2); if (!testOk1((len == want) && (strncmp(msg1, cbuf, len) == 0))) testDiag("wanted:%d '%.*s' got:%d '%.*s'", want, want, msg1, len, len, cbuf); - q1->send((void *)msg1, i++, 1.0); - testOk1(q1->pending() == 3); + q1.send((void *)msg1, i++, 1.0); + testOk1(q1.pending() == 3); i = 3; - while ((len = q1->receive(cbuf, sizeof cbuf, 1.0)) >= 0) { + while ((len = q1.receive(cbuf, sizeof cbuf, 1.0)) >= 0) { --i; - testOk(q1->pending() == i, "q1->pending() == %d", i); + testOk(q1.pending() == i, "loop: q1.pending() == %d", i); want++; if (!testOk1((len == want) && (strncmp(msg1, cbuf, len) == 0))) testDiag("wanted:%d '%.*s' got:%d '%.*s'", want, want, msg1, len, len, cbuf); } - testOk1(q1->pending() == 0); + testOk1(q1.pending() == 0); testDiag("Test receiver with timeout:"); for (i = 0 ; i < 4 ; i++) - testOk1 (q1->send((void *)msg1, i, 1.0) == 0); - testOk1(q1->pending() == 4); + testOk1 (q1.send((void *)msg1, i, 1.0) == 0); + testOk1(q1.pending() == 4); for (i = 0 ; i < 4 ; i++) - testOk(q1->receive((void *)cbuf, sizeof cbuf, 1.0) == (int)i, - "q1->receive(...) == %d", i); - testOk1(q1->pending() == 0); - testOk1(q1->receive((void *)cbuf, sizeof cbuf, 1.0) < 0); - testOk1(q1->pending() == 0); + testOk(q1.receive((void *)cbuf, sizeof cbuf, 1.0) == (int)i, + "q1.receive(...) == %d", i); + testOk1(q1.pending() == 0); + testOk1(q1.receive((void *)cbuf, sizeof cbuf, 1.0) < 0); + testOk1(q1.pending() == 0); testDiag("Single receiver with invalid size, single sender tests:"); - epicsThreadCreate("Bad Receiver", epicsThreadPriorityMedium, - mediumStack, badReceiver, q1); + rxThread = epicsThreadCreateOpt("Bad Receiver", badReceiver, &q1, &opts); + if (!rxThread) + testAbort("epicsThreadCreate failed"); epicsThreadSleep(1.0); - testOk(q1->send((void *)msg1, 10) == 0, "Send with waiting receiver"); - epicsThreadSleep(2.0); - testOk(q1->send((void *)msg1, 10) == 0, "Send with no receiver"); + testOk(q1.send((void *)msg1, 10) == 0, "Send with waiting receiver"); epicsThreadSleep(2.0); + testOk(q1.send((void *)msg1, 10) == 0, "Send with no receiver"); + epicsThreadMustJoin(rxThread); testDiag("6 Single receiver single sender 'Sleepy timeout' tests,"); testDiag(" these should take about %.2f seconds each:", SLEEPY_TESTS * 0.010); - complete = epicsEventMustCreate(epicsEventEmpty); sleepySender(0.009); sleepySender(0.010); sleepySender(0.011); @@ -375,11 +378,12 @@ extern "C" void messageQueueTest(void *parm) testDiag("Single receiver, single sender tests:"); epicsThreadSetPriority(myThreadId, epicsThreadPriorityHigh); - epicsThreadCreate("Receiver one", epicsThreadPriorityMedium, - mediumStack, receiver, q1); + rxThread = epicsThreadCreateOpt("Receiver one", receiver, &q1, &opts); + if (!rxThread) + testAbort("epicsThreadCreate failed"); for (pass = 1 ; pass <= 3 ; pass++) { for (i = 0 ; i < 10 ; i++) { - if (q1->trySend((void *)msg1, i) < 0) + if (q1.trySend((void *)msg1, i) < 0) break; if (pass >= 3) epicsThreadSleep(0.5); @@ -408,18 +412,20 @@ extern "C" void messageQueueTest(void *parm) */ testDiag("Single receiver, multiple sender tests:"); testDiag("This test lasts 30 seconds..."); - testOk(!!epicsThreadCreate("Sender 1", epicsThreadPriorityLow, - mediumStack, sender, q1), - "Created Sender 1"); - testOk(!!epicsThreadCreate("Sender 2", epicsThreadPriorityMedium, - mediumStack, sender, q1), - "Created Sender 2"); - testOk(!!epicsThreadCreate("Sender 3", epicsThreadPriorityHigh, - mediumStack, sender, q1), - "Created Sender 3"); - testOk(!!epicsThreadCreate("Sender 4", epicsThreadPriorityHigh, - mediumStack, sender, q1), - "Created Sender 4"); + for (i=0; i #include +#include +#include "epicsAssert.h" +#include "dbDefs.h" #include "osiSock.h" +#include "epicsTime.h" +#include "epicsThread.h" #include "epicsUnitTest.h" #include "testMain.h" /* This could easily be generalized to test more options */ +static void udpBroadcast(SOCKET s, int put) { int status; @@ -27,6 +33,7 @@ void udpBroadcast(SOCKET s, int put) "getsockopt BROADCAST => %d", flag); } +static void multiCastLoop(SOCKET s, int put) { int status; @@ -42,6 +49,7 @@ void multiCastLoop(SOCKET s, int put) "getsockopt MULTICAST_LOOP => %d", (int) flag); } +static void multiCastTTL(SOCKET s, int put) { int status; @@ -57,10 +65,13 @@ void multiCastTTL(SOCKET s, int put) "getsockopt IP_MULTICAST_TTL => %d", (int) flag); } +static void udpSockTest(void) { SOCKET s; + testDiag("udpSockTest()"); + s = epicsSocketCreate(AF_INET, SOCK_DGRAM, 0); testOk(s != INVALID_SOCKET, "epicsSocketCreate INET, DGRAM, 0"); @@ -107,11 +118,43 @@ int doBind(int expect, SOCKET S, unsigned* port) } } -void udpSockFanoutTest(void) +static +void tcpSockReuseBindTest(int reuse) +{ + SOCKET A, B; + unsigned port=0; /* choose random port */ + + testDiag("tcpSockReuseBindTest(%d)", reuse); + + A = epicsSocketCreate(AF_INET, SOCK_STREAM, 0); + B = epicsSocketCreate(AF_INET, SOCK_STREAM, 0); + + if(A==INVALID_SOCKET || B==INVALID_SOCKET) + testAbort("Insufficient sockets"); + + if(reuse) { + testDiag("epicsSocketEnableAddressReuseDuringTimeWaitState"); + epicsSocketEnableAddressReuseDuringTimeWaitState(A); + epicsSocketEnableAddressReuseDuringTimeWaitState(B); + } + + doBind(0, A, &port); + if(listen(A, 4)) + testFail("listen(A) -> %d", (int)SOCKERRNO); + doBind(1, B, &port); + + epicsSocketDestroy(A); + epicsSocketDestroy(B); +} + +static +void udpSockFanoutBindTest(void) { SOCKET A, B, C; unsigned port=0; /* choose random port */ + testDiag("udpSockFanoutBindTest()"); + A = epicsSocketCreate(AF_INET, SOCK_DGRAM, 0); B = epicsSocketCreate(AF_INET, SOCK_DGRAM, 0); C = epicsSocketCreate(AF_INET, SOCK_DGRAM, 0); @@ -138,16 +181,233 @@ void udpSockFanoutTest(void) epicsSocketDestroy(C); } +struct CASearch { + epicsUInt16 cmd, size, dtype, dcnt; + epicsUInt32 p1, p2; + char body[16]; +}; + +STATIC_ASSERT(sizeof(struct CASearch)==32); + +union CASearchU { + struct CASearch msg; + char bytes[sizeof(struct CASearch)]; +}; + +static +unsigned nsuccess; + +static +const unsigned nrepeat = 6u; + +struct TInfo { + SOCKET sock; + unsigned id; + epicsUInt32 key; + epicsUInt8 rxmask; + epicsUInt8 dupmask; +}; + +static +void udpSockFanoutTestRx(void* raw) +{ + struct TInfo *info = raw; + epicsTimeStamp start, now; +#ifdef _WIN32 + /* ms */ + DWORD timeout = 10000; +#else + struct timeval timeout; + memset(&timeout, 0, sizeof(struct timeval)); + timeout.tv_sec = 10; + timeout.tv_usec = 0; +#endif + unsigned nremain = nrepeat; + + (void)epicsTimeGetCurrent(&start); + now = start; + testDiag("RX%u start", info->id); + + if(setsockopt(info->sock, SOL_SOCKET, SO_RCVTIMEO, (void*)&timeout, sizeof(timeout))) { + testFail("Unable to set socket timeout"); + return; + } + + while(epicsTimeDiffInSeconds(&now, &start)<=5.0) { + union CASearchU buf; + osiSockAddr src; + osiSocklen_t srclen = sizeof(src); + + int n = recvfrom(info->sock, buf.bytes, sizeof(buf.bytes), 0, &src.sa, &srclen); + buf.bytes[sizeof(buf.bytes)-1] = '\0'; + + if(n<0) { + testDiag("recvfrom error (%d)", (int)SOCKERRNO); + break; + } else if((n==sizeof(buf.bytes)) && buf.msg.cmd==htons(6) && buf.msg.size==htons(16) + &&buf.msg.dtype==htons(5) && buf.msg.dcnt==0 && strcmp(buf.msg.body, "totallyinvalid")==0) + { + unsigned ord = ntohl(buf.msg.p1)-info->key; + testDiag("RX%u success %u", info->id, ord); + if(ord<8) { + const epicsUInt8 mask = 1u<rxmask&mask) + info->dupmask|=mask; + info->rxmask|=mask; + } + if(0==--nremain) + break; + } else { + testDiag("RX ignore"); + } + } + testDiag("RX%u end", info->id); +} + +static +void udpSockFanoutTestIface(const osiSockAddr* addr) +{ + SOCKET sender; + struct TInfo rx1, rx2; + epicsThreadId trx1, trx2; + epicsThreadOpts topts = EPICS_THREAD_OPTS_INIT; + int opt = 1; + unsigned i; + osiSockAddr any; + epicsUInt32 key = 0xdeadbeef ^ ntohl(addr->ia.sin_addr.s_addr); + union CASearchU buf; + + topts.joinable = 1; + + /* we bind to any for lack of a portable way to find the + * interface address from the interface broadcast address + */ + memset(&any, 0, sizeof(any)); + any.ia.sin_family = AF_INET; + any.ia.sin_addr.s_addr = htonl(INADDR_ANY); + any.ia.sin_port = addr->ia.sin_port; + + buf.msg.cmd = htons(6); + buf.msg.size = htons(16); + buf.msg.dtype = htons(5); + buf.msg.dcnt = htons(0); /* version 0, which newer servers should ignore */ + /* .p1 and .p2 set below */ + memcpy(buf.msg.body, "tota" "llyi" "nval" "id\0\0", 16); + + sender = epicsSocketCreate(AF_INET, SOCK_DGRAM, 0); + rx1.sock = epicsSocketCreate(AF_INET, SOCK_DGRAM, 0); + rx2.sock = epicsSocketCreate(AF_INET, SOCK_DGRAM, 0); + if((sender==INVALID_SOCKET) || (rx1.sock==INVALID_SOCKET) || (rx2.sock==INVALID_SOCKET)) + testAbort("Unable to allocate test socket(s)"); + + rx1.id = 1; + rx2.id = 2; + rx1.key = rx2.key = key; + rx1.rxmask = rx2.rxmask = 0u; + rx1.dupmask = rx2.dupmask = 0u; + + if(setsockopt(sender, SOL_SOCKET, SO_BROADCAST, (void*)&opt, sizeof(opt))!=0) { + testFail("setsockopt SOL_SOCKET, SO_BROADCAST error -> %d", (int)SOCKERRNO); + } + + epicsSocketEnableAddressUseForDatagramFanout(rx1.sock); + epicsSocketEnableAddressUseForDatagramFanout(rx2.sock); + + if(bind(rx1.sock, &any.sa, sizeof(any))) + testFail("Can't bind test socket rx1 %d", (int)SOCKERRNO); + if(bind(rx2.sock, &any.sa, sizeof(any))) + testFail("Can't bind test socket rx2 %d", (int)SOCKERRNO); + + trx1 = epicsThreadCreateOpt("rx1", &udpSockFanoutTestRx, &rx1, &topts); + trx2 = epicsThreadCreateOpt("rx2", &udpSockFanoutTestRx, &rx2, &topts); + + for(i=0; isa, sizeof(*addr)); + if(ret!=(int)sizeof(buf.bytes)) + testDiag("sendto() error %d (%d)", ret, (int)SOCKERRNO); + } + + epicsThreadMustJoin(trx1); + epicsThreadMustJoin(trx2); + + testDiag("Result: RX1 %x:%x RX2 %x:%x", + rx1.rxmask, rx1.dupmask, rx2.rxmask, rx2.dupmask); + /* success if any one packet was seen by both sockets */ + if(rx1.rxmask & rx2.rxmask) + nsuccess++; + + epicsSocketDestroy(sender); + epicsSocketDestroy(rx1.sock); + epicsSocketDestroy(rx2.sock); +} + +/* This test violates the principle of unittest isolation by broadcasting + * on the well known CA search port on all interfaces. There is no + * portable way to avoid this. (eg. 127.255.255.255 is Linux specific) + */ +static +void udpSockFanoutTest() +{ + ELLLIST ifaces = ELLLIST_INIT; + ELLNODE *cur; + SOCKET dummy; + osiSockAddr match; + int foundNotLo = 0; + + testDiag("udpSockFanoutTest()"); + + memset(&match, 0, sizeof(match)); + match.ia.sin_family = AF_INET; + match.ia.sin_addr.s_addr = htonl(INADDR_ANY); + + if((dummy = epicsSocketCreate(AF_INET, SOCK_DGRAM, 0))==INVALID_SOCKET) + testAbort("Unable to allocate discovery socket"); + + osiSockDiscoverBroadcastAddresses(&ifaces, dummy, &match); + + for(cur = ellFirst(&ifaces); cur; cur = ellNext(cur)) { + char name[64]; + osiSockAddrNode* node = CONTAINER(cur, osiSockAddrNode, node); + + node->addr.ia.sin_port = htons(5064); + (void)sockAddrToDottedIP(&node->addr.sa, name, sizeof(name)); + + testDiag("Interface %s", name); + if(node->addr.ia.sin_addr.s_addr!=htonl(INADDR_LOOPBACK)) { + testDiag("Not LO"); + foundNotLo = 1; + } + + udpSockFanoutTestIface(&node->addr); + } + + ellFree(&ifaces); + + testOk(foundNotLo, "Found non-loopback interface"); + testOk(nsuccess>0, "Successes %u", nsuccess); + + epicsSocketDestroy(dummy); +} + MAIN(osiSockTest) { int status; - testPlan(18); + testPlan(24); status = osiSockAttach(); testOk(status, "osiSockAttach"); udpSockTest(); + udpSockFanoutBindTest(); udpSockFanoutTest(); + tcpSockReuseBindTest(0); + tcpSockReuseBindTest(1); osiSockRelease(); return testDone();