diff --git a/.appveyor.yml b/.appveyor.yml index f384361fc..c6151850f 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -31,6 +31,8 @@ skip_commits: - 'documentation/*' - 'startup/*' - '.github/*' + - '.tools/*' + - '.gitattributes' - '**/*.html' - '**/*.md' diff --git a/.appveyor/epics-base-7.yml b/.appveyor/epics-base-7.yml index 410989d78..1a01b35d5 100644 --- a/.appveyor/epics-base-7.yml +++ b/.appveyor/epics-base-7.yml @@ -38,6 +38,8 @@ skip_commits: - 'documentation/*' - 'startup/*' - '.github/*' + - '.tools/*' + - '.gitattributes' - '**/*.html' - '**/*.md' diff --git a/.gitattributes b/.gitattributes index 5842dd19a..d71c782e0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,6 @@ .ci/ export-ignore .tools/ export-ignore +.github/ export-ignore +.appveyor/ export-ignore .appveyor.yml export-ignore -.travis.yml export-ignore README export-subst diff --git a/.github/workflows/ci-scripts-build.yml b/.github/workflows/ci-scripts-build.yml index 98cdfc75c..7892a24e0 100644 --- a/.github/workflows/ci-scripts-build.yml +++ b/.github/workflows/ci-scripts-build.yml @@ -14,9 +14,19 @@ on: - 'documentation/*' - 'startup/*' - '.appveyor/*' + - '.tools/*' + - '.gitattributes' - '**/*.html' - '**/*.md' pull_request: + paths-ignore: + - 'documentation/*' + - 'startup/*' + - '.appveyor/*' + - '.tools/*' + - '.gitattributes' + - '**/*.html' + - '**/*.md' env: SETUP_PATH: .ci-local:.ci @@ -165,6 +175,12 @@ jobs: configuration: default name: "Ub-20 gcc-8" + - os: ubuntu-20.04 + cmp: gcc-9 + utoolchain: "9" + configuration: default + name: "Ub-20 gcc-9" + - os: ubuntu-20.04 cmp: clang configuration: default @@ -185,6 +201,11 @@ jobs: configuration: static name: "Win2019 MSC-19, static" + - os: windows-2019 + cmp: gcc + configuration: default + name: "Win2019 mingw" + steps: - uses: actions/checkout@v2 with: @@ -211,6 +232,7 @@ jobs: - name: Run main module tests run: python .ci/cue.py test - name: Upload tapfiles Artifact + if: ${{ always() }} uses: actions/upload-artifact@v2 with: name: tapfiles ${{ matrix.name }} diff --git a/configure/CONFIG_BASE_VERSION b/configure/CONFIG_BASE_VERSION index f1724487b..b0c57b763 100644 --- a/configure/CONFIG_BASE_VERSION +++ b/configure/CONFIG_BASE_VERSION @@ -48,11 +48,11 @@ EPICS_VERSION = 7 EPICS_REVISION = 0 # EPICS_MODIFICATION must be a number >=0 and <256 -EPICS_MODIFICATION = 4 +EPICS_MODIFICATION = 5 # EPICS_PATCH_LEVEL must be a number (win32 resource file requirement) # Not included in the official EPICS version number if zero -EPICS_PATCH_LEVEL = 2 +EPICS_PATCH_LEVEL = 1 # Immediately after an official release the EPICS_PATCH_LEVEL is incremented # and the -DEV suffix is added (similar to the Maven -SNAPSHOT versions) diff --git a/configure/CONFIG_CA_VERSION b/configure/CONFIG_CA_VERSION index 8aae1653b..af570b0e7 100644 --- a/configure/CONFIG_CA_VERSION +++ b/configure/CONFIG_CA_VERSION @@ -2,7 +2,7 @@ EPICS_CA_MAJOR_VERSION = 4 EPICS_CA_MINOR_VERSION = 13 -EPICS_CA_MAINTENANCE_VERSION = 8 +EPICS_CA_MAINTENANCE_VERSION = 9 # Development flag, set to zero for release versions diff --git a/configure/CONFIG_DATABASE_VERSION b/configure/CONFIG_DATABASE_VERSION index 56768f08d..2205a5386 100644 --- a/configure/CONFIG_DATABASE_VERSION +++ b/configure/CONFIG_DATABASE_VERSION @@ -1,8 +1,8 @@ # Version number for the database APIs and shared library EPICS_DATABASE_MAJOR_VERSION = 3 -EPICS_DATABASE_MINOR_VERSION = 18 -EPICS_DATABASE_MAINTENANCE_VERSION = 2 +EPICS_DATABASE_MINOR_VERSION = 19 +EPICS_DATABASE_MAINTENANCE_VERSION = 1 # Development flag, set to zero for release versions diff --git a/configure/CONFIG_LIBCOM_VERSION b/configure/CONFIG_LIBCOM_VERSION index 92aba5c11..96f25ebaa 100644 --- a/configure/CONFIG_LIBCOM_VERSION +++ b/configure/CONFIG_LIBCOM_VERSION @@ -1,8 +1,8 @@ # Version number for the libcom APIs and shared library EPICS_LIBCOM_MAJOR_VERSION = 3 -EPICS_LIBCOM_MINOR_VERSION = 18 -EPICS_LIBCOM_MAINTENANCE_VERSION = 2 +EPICS_LIBCOM_MINOR_VERSION = 19 +EPICS_LIBCOM_MAINTENANCE_VERSION = 1 # Development flag, set to zero for release versions diff --git a/configure/RULES.Db b/configure/RULES.Db index 42a57430c..406c1eb16 100644 --- a/configure/RULES.Db +++ b/configure/RULES.Db @@ -196,12 +196,12 @@ endif #--------------------------------------------------------------- # build dependancies, clean rule -inc: $(COMMON_INC) $(INSTALL_INC) +inc: $(COMMON_INC) $(INSTALL_INC) $(COMMON_DBDS) $(COMMON_DBDCATS) \ + $(INSTALL_DBDS) $(INSTALL_DBD_INSTALLS) -build: $(COMMON_DBDS) $(COMMON_DBS) $(COMMON_DBDCATS) \ - $(INSTALL_DBDS) $(INSTALL_DBS) \ +build: $(COMMON_DBS) $(INSTALL_DBS) \ $(DBDDEPENDS_FILES) $(TARGETS) \ - $(INSTALL_DB_INSTALLS) $(INSTALL_DBD_INSTALLS) + $(INSTALL_DB_INSTALLS) clean: db_clean diff --git a/configure/RULES_BUILD b/configure/RULES_BUILD index bbd8e11d1..53297af52 100644 --- a/configure/RULES_BUILD +++ b/configure/RULES_BUILD @@ -103,17 +103,20 @@ include $(CONFIG)/RULES.Db #--------------------------------------------------------------- # Include defines and rules for prod, library and test* targets -#ifneq (,$(strip $(PROD) $(TESTPROD) $(LIBRARY) $(TESTLIBRARY) $(LOADABLE_LIBRARY) )) +ifneq (,$(strip $(PROD) $(TESTPROD) $(LIBRARY) $(TESTLIBRARY) \ + $(LOADABLE_LIBRARY))) include $(CONFIG)/RULES_TARGET -#endif +endif #--------------------------------------------------------------- # Read dependency files +ifneq (inc,$(strip $(MAKECMDGOALS))) ifneq (,$(strip $(HDEPENDS_FILES))) $(filter-out $(wildcard *$(DEP)), $(HDEPENDS_FILES)): | $(COMMON_INC) -include $(HDEPENDS_FILES) endif +endif #--------------------------------------------------------------- # Products and Object libraries @@ -161,12 +164,13 @@ build: inc build: $(OBJSNAME) $(LIBTARGETS) $(PRODTARGETS) $(TESTPRODTARGETS) \ $(TARGETS) $(TESTSCRIPTS) $(INSTALL_LIB_INSTALLS) -inc: $(COMMON_INC) $(INSTALL_INC) $(INSTALL_CONFIGS) +inc: $(COMMON_INC) $(INSTALL_INC) $(INSTALL_CONFIGS) $(INSTALLS_CFG) \ + $(INSTALL_HTMLS) $(INSTALLS_PERL_MODULES) $(INSTALL_SCRIPTS) buildInstall: \ - $(INSTALL_SCRIPTS) $(INSTALL_PROD) $(INSTALL_MUNCHS) \ + $(INSTALL_PROD) $(INSTALL_MUNCHS) \ $(INSTALL_TCLLIBS) $(INSTALL_TCLINDEX) \ - $(INSTALL_HTMLS) $(INSTALL_DOCS) \ + $(INSTALL_DOCS) \ $(INSTALL_OBJS) \ $(INSTALL_TEMPLATE) \ $(INSTALL_BIN_INSTALLS) diff --git a/configure/RULES_TARGET b/configure/RULES_TARGET index 5d77b105e..06f8a447d 100644 --- a/configure/RULES_TARGET +++ b/configure/RULES_TARGET @@ -21,7 +21,6 @@ $(foreach target, $(PROD) $(TESTPROD) $(LIBRARY) $(TESTLIBRARY) $(LOADABLE_LIBRA #----------------------------------------------------------------------- -# This define block requires GNU make 3.81 define PROD_template ifeq ($$(strip $$($(1)_OBJS) $$($(1)_SRCS) $$(PRODUCT_OBJS)),) $(1)_OBJS = $(1)$$(OBJ) diff --git a/configure/RULES_TOP b/configure/RULES_TOP index ec0597a49..60b52e0de 100644 --- a/configure/RULES_TOP +++ b/configure/RULES_TOP @@ -73,7 +73,7 @@ help: @echo "Usage: gnumake [options] [target] ..." @echo "Targets supported by all Makefiles:" @echo " all - Same as install (default rule)" - @echo " inc - Installs header files" + @echo " inc - Installs header, dbd and html files" @echo " build - Builds and installs all targets" @echo " install - Builds and installs all targets" @echo " buildInstall - Same as install (deprecated)" diff --git a/documentation/Doxyfile@ b/documentation/Doxyfile@ index 482564264..bf53ee0ce 100644 --- a/documentation/Doxyfile@ +++ b/documentation/Doxyfile@ @@ -107,7 +107,7 @@ BRIEF_MEMBER_DESC = YES # brief descriptions will be completely suppressed. # The default value is: YES. -REPEAT_BRIEF = YES +REPEAT_BRIEF = NO # This tag implements a quasi-intelligent brief description abbreviator that is # used to form the text in various listings. Each string in this list, if found @@ -143,7 +143,7 @@ ALWAYS_DETAILED_SEC = NO # operators of the base classes will not be shown. # The default value is: NO. -INLINE_INHERITED_MEMB = NO +INLINE_INHERITED_MEMB = YES # If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the @@ -162,8 +162,8 @@ FULL_PATH_NAMES = YES # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. -STRIP_FROM_PATH = \ - ../ +STRIP_FROM_PATH = @TOP@/include \ + .. # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which @@ -172,7 +172,8 @@ STRIP_FROM_PATH = \ # specify the list of include paths that are normally passed to the compiler # using the -I flag. -STRIP_FROM_INC_PATH = ../ +STRIP_FROM_INC_PATH = @TOP@/include \ + .. # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't @@ -227,7 +228,7 @@ SEPARATE_MEMBER_PAGES = NO # uses this value to replace tabs by spaces in code fragments. # Minimum value: 1, maximum value: 16, default value: 4. -TAB_SIZE = 4 +TAB_SIZE = 8 # This tag can be used to specify a number of aliases that act as commands in # the documentation. An alias has the form: @@ -241,12 +242,6 @@ TAB_SIZE = 4 ALIASES = -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding "class=itcl::class" -# will allow you to use the command class in the itcl::class meaning. - -TCL_SUBST = - # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all @@ -420,7 +415,7 @@ LOOKUP_CACHE_SIZE = 0 # normally produced when WARNINGS is set to YES. # The default value is: NO. -EXTRACT_ALL = YES +EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES all private members of a class will # be included in the documentation. @@ -548,7 +543,7 @@ INLINE_INFO = YES # name. If set to NO the members will appear in declaration order. # The default value is: YES. -SORT_MEMBER_DOCS = YES +SORT_MEMBER_DOCS = NO # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member @@ -767,7 +762,8 @@ WARN_LOGFILE = INPUT = ../mainpage.dox \ ../RELEASE_NOTES.md \ ../README.md \ - ../RecordReference.md + ../RecordReference.md \ + @TOP@/include # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -787,48 +783,10 @@ INPUT_ENCODING = UTF-8 # *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, # *.qsf, *.as and *.js. -FILE_PATTERNS = *.c \ - *.cc \ - *.cxx \ - *.cpp \ - *.c++ \ - *.java \ - *.ii \ - *.ixx \ - *.ipp \ - *.i++ \ - *.inl \ - *.idl \ - *.ddl \ - *.odl \ - *.h \ - *.hh \ - *.hxx \ +FILE_PATTERNS = *.h \ *.hpp \ - *.h++ \ - *.cs \ - *.d \ - *.php \ - *.php4 \ - *.php5 \ - *.phtml \ - *.inc \ - *.m \ - *.markdown \ *.md \ - *.mm \ - *.dox \ - *.py \ - *.f90 \ - *.f \ - *.for \ - *.tcl \ - *.vhd \ - *.vhdl \ - *.ucf \ - *.qsf \ - *.as \ - *.js + *.dox # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. @@ -843,7 +801,8 @@ RECURSIVE = YES # Note that relative paths are relative to the directory from which doxygen is # run. -EXCLUDE = +EXCLUDE = @TOP@/include/pv \ + @TOP@/include/pva # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -859,7 +818,7 @@ EXCLUDE_SYMLINKS = NO # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = O.* +EXCLUDE_PATTERNS = /O.*/ # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the @@ -2028,7 +1987,7 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = +PREDEFINED = __cplusplus # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The @@ -2047,7 +2006,7 @@ EXPAND_AS_DEFINED = # The default value is: YES. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -SKIP_FUNCTION_MACROS = YES +SKIP_FUNCTION_MACROS = NO #--------------------------------------------------------------------------- # Configuration options related to external references @@ -2094,12 +2053,6 @@ EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES -# The PERL_PATH should be the absolute path and name of the perl script -# interpreter (i.e. the result of 'which perl'). -# The default file (with absolute path) is: /usr/bin/perl. - -PERL_PATH = /usr/bin/perl - #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- @@ -2113,15 +2066,6 @@ PERL_PATH = /usr/bin/perl CLASS_DIAGRAMS = YES -# You can define message sequence charts within doxygen comments using the \msc -# command. Doxygen will then run the mscgen tool (see: -# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the -# documentation. The MSCGEN_PATH tag allows you to specify the directory where -# the mscgen tool resides. If left empty the tool is assumed to be found in the -# default search path. - -MSCGEN_PATH = - # You can include diagrams made with dia in doxygen documentation. Doxygen will # then run dia to produce the diagram and insert it in the documentation. The # DIA_PATH tag allows you to specify the directory where the dia binary resides. diff --git a/documentation/Makefile b/documentation/Makefile index 8148687cb..bd527e7db 100644 --- a/documentation/Makefile +++ b/documentation/Makefile @@ -1,22 +1,24 @@ TOP = .. include $(TOP)/configure/CONFIG -ifdef T_A +ifeq ($(T_A),$(EPICS_HOST_ARCH)) DOXYGEN=doxygen -EXPAND = Doxyfile +EXPAND = Doxyfile@ EXPAND_ME += EPICS_VERSION EXPAND_ME += EPICS_REVISION EXPAND_ME += EPICS_MODIFICATION EXPAND_ME += EPICS_PATCH_LEVEL +EXPAND_ME += OS_CLASS CMPLR_CLASS ME = documentation/O.$(T_A)/html +GH_FILES = $(ME)/ $(ME)/.nojekyll $(ME)/*.* $(ME)/*/*.* install: doxygen -doxygen: Doxyfile +doxygen: Doxyfile ../mainpage.dox $(DOXYGEN) rsync -av $(TOP)/html/ html/ @@ -24,10 +26,10 @@ doxygen: Doxyfile commit: doxygen $(TOUCH) html/.nojekyll - (cd $(TOP) && $(CURDIR)/../commit-gh.sh $(ME)/ $(ME)/.nojekyll $(ME)/*.* $(ME)/*/*.*) + (cd $(TOP) && $(CURDIR)/../commit-gh.sh $(GH_FILES)) .PHONY: commit -endif # T_A +endif # EPICS_HOST_ARCH include $(TOP)/configure/RULES diff --git a/documentation/README.md b/documentation/README.md index dabee1759..7e995bfde 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -1,6 +1,6 @@ # Installation Instructions {#install} -## EPICS Base Release 7.0.4.1 +## EPICS Base Release 7.0.5 ----- @@ -12,7 +12,6 @@ - [Supported platforms](#0_0_4) - [Supported compilers](#0_0_5) - [Software requirements](#0_0_6) - - [Host system storage requirements](#0_0_7) - [Documentation](#0_0_8) - [Directory Structure](#0_0_10) - [Build related components](#0_0_11) @@ -39,7 +38,7 @@ interfaces) of various types. Please check the `RELEASE_NOTES` file in the distribution for description of changes and release migration details. -### Copyright +### Copyright Licenses Please review the LICENSE file included in the distribution for legal terms of usage. @@ -68,10 +67,10 @@ path to do EPICS builds; check the definitions of CC and CCC in **GNU make** You must use GNU make, gnumake, for any EPICS builds. Set your path so -that a gnumake version 3.81 or later is available. +that a gnumake version 4.1 or later is available. **Perl** -You must have Perl version 5.8.1 or later installed. The EPICS +You must have Perl version 5.10 or later installed. The EPICS configuration files do not specify the perl full pathname, so the perl executable must be found through your normal search path. @@ -114,13 +113,6 @@ installed on linux-x86. Command-line editing and history will then be those supplied by the os. On vxWorks the ledLib command-line input library is used instead. -### Host system storage requirements - -The compressed tar file is approximately 1.6 MB in size. The -distribution source tree takes up approximately 12 MB. Each host -target will need around 40 MB for build files, and each cross-compiled -target around 20 MB. - ### Documentation EPICS documentation is available through the [EPICS @@ -242,17 +234,13 @@ Before you can build or use this EPICS base, the environment variable the base/startup directory has been provided to help set `EPICS_HOST_ARCH.` You should have `EPICS_HOST_ARCH` set to your host operating system followed by a dash and then your host -architecture, e.g. solaris-sparc. If you are not using the OS +architecture, e.g. linux-x86_64. If you are not using the OS vendor's c/c++ compiler for host builds, you will need another dash followed by the alternate compiler name (e.g. "-gnu" for GNU c/c++ compilers on a solaris host or "-mingw" for MinGW c/c++ compilers on -a WIN32 host). See `configure/CONFIG_SITE` for a list of supported +Windows). See `configure/CONFIG_SITE` for a list of supported `EPICS_HOST_ARCH` values. -* `PERLLIB` -On WIN32, some versions of Perl require that the environment -variable PERLLIB be set to <perl directory location>. - * `PATH` As already mentioned, you must have the perl executable and you may need C and C++ compilers in your search path. For building base you diff --git a/documentation/RELEASE_NOTES.md b/documentation/RELEASE_NOTES.md index 1f549f3e0..e39b4196e 100644 --- a/documentation/RELEASE_NOTES.md +++ b/documentation/RELEASE_NOTES.md @@ -13,7 +13,7 @@ should also be read to understand what has changed since earlier releases. **This version of EPICS has not been released yet.** -## Changes made on the 7.0 branch since 7.0.4.1 +## Changes made on the 7.0 branch since 7.0.5 @@ -56,21 +56,79 @@ Known Issues: issues with DHCP, but network stack usable. Can load env from NVRAM. +----- -### Priority inversion safe posix mutexes +## EPICS Release 7.0.5 + +### Fix aai's Device Support Initialization + +Krisztian Loki [reported](https://github.com/epics-base/epics-base/issues/97) +segfaults occurring when a Soft Channel aai record INP field was a DB link to +an array field of a compress record. This was caused by the aai record's +pass-0 device support initialization clashing with the semantics of the new +link support API. + +The aai record +[has been modified](https://github.com/epics-base/epics-base/pull/114) to +allow the Soft Channel device support to request a pass-1 initialization +callback. See the Device Support section of the Array Analogue Input Record +Reference pages in this release for the API changes, which are fully backwards +compatible for existing aai device support. + +### Prevent default DTYPs from changing + +[Kay Kasemir reported](https://bugs.launchpad.net/epics-base/+bug/1908305) that +it is possible to change the Base record type's default DTYP if a `device()` +entry is seen before the `recordtype()` definition to which it refers. The +default DTYP is the first device loaded, which is normally the `Soft Channel` +support from Base. A warning was being displayed by dbdExpand when a `device()` +entry was see first, but that was easily missed. + +The DBD file parser in dbdExpand.pl has now been modified to make this an error, +although the registerRecordDeviceDriver.pl script will still accept `device()` +entries without having their `recordtype()` loaded since this is necessary to +compile device supports as loadable modules. + + +### Priority inversion safe Posix mutexes On Posix systems, epicsMutex now support priority inheritance if available. -The IOC needs to run with SCHED_FIFO engaged. -Support for Posix implementations before POSIX.1-2001 (_XOPEN_SOURCE < 500, -glibc version < 2.3.3) has been dropped. +The IOC needs to run with SCHED_FIFO engaged to use these. +Support for Posix implementations before POSIX.1-2001 (`_XOPEN_SOURCE < 500`, +glibc version < 2.3.3) has been dropped. -The epicsMutexShowAll() function (available through IOC shell) -will print "PI is enabled" if both libc and kernel support is present. +The IOC shell's `epicsMutexShowAll` command prints "PI is enabled" if both +libc and kernel support is present. -### Add epicsStrSimilarity() +### Fix for Periodic Scan threads hanging on Windows -Add epicsStrSimilarity() to epicsString.h which uses edit distance as an approximate comparison. -Enables a new "Did you mean ..." suggestion when a .db file provides an invalid value for a DBF_MENU or DBF_DEVICE field. +Since 7.0.3.1 a Windows IOC could not run for more than 49.7 days; at that +time the periodic scan threads would stop processing. This issue should now +have been fixed and the Monotonic time functions on Windows should return +values which count at nanosecond resolution. However we have not waited 49.7 +days to test the final software, so there is a small chance that it's still +broken. + +This fixes [lauchpad bug #1896295](https://bugs.launchpad.net/bugs/1896295). + +### Support for Apple M1 (arm64) Processors + +Thanks to Jeong Han Lee this release comes with build support for Apple's new +M1 CPUs running macOS, using the target name `darwin-aarch64`. + +It should also be possible to build universal binaries containing code for +both the Intel and arm64 processors under either target name: In the +appropriate `configure/os/CONFIG_SITE.Common.darwin-*` file add the other +architecture class name to the `ARCH_CLASS` variable (after a space). + +### New String Comparison Routine `epicsStrSimilarity()` + +The new `epicsStrSimilarity()` routine in epicsString.h uses a modified +Levenshtein distance to compare two strings, with a character case difference +being half the weight of a full substitution. The double return value falls in +the range 0.0 (identical) through 1.0 (no characters matching), or -1.0 for +error. This is used to provide a new "Did you mean ..." suggestion when a .db +file provides an invalid choice string for a `DBF_MENU` or `DBF_DEVICE` field. ### Build System: New `VALID_BUILDS` type "Command" @@ -234,32 +292,35 @@ as an array field, and its record support must define a `put_array_info()` routine. ### Timestamp before processing output links -The record processing code for records with output links has been modified -to update the timestamp via recGblGetTimeStamp() before processing the -output links. This ensures that other records which get processed via -the output link can use TSEL links to fetch the timestamp which corresponds -to the data processed by the output link. + +The record processing code for records with output links has been modified to +update the timestamp via recGblGetTimeStamp() _before_ processing the output +links. This ensures that other records which get processed via an output link +can use TSEL links to fetch the timestamp corresponding to the data processed +by the output link. This change could result in a slightly earlier timestamp for records whose output link is handled by a device driver, but only if the device driver does -not handle its own timestamping via TSE -2 and instead uses TSE 0 or TSE -1 -to get current time or best time, and the time spent in the device driver is +not handle its own timestamping via TSE -2 and instead uses TSE 0 or TSE -1 to +get current time or best time, and the time spent in the device driver is greater than your timestamp provider resolution. For these situations it is recommended to set TSE to -2 and set the timestamp in the driver code. ### Add registerAllRecordDeviceDrivers() -Addition of registerAllRecordDeviceDrivers() as an iocsh function -and in iocshRegisterCommon.h. This function uses dynamic lookup with -`epicsFindSymbol()` to perform the same function as a generated -`*_registerRecordDeviceDriver()` function. -This allows dynamic loading/linking of support modules without code generation. +A new iocsh command `registerAllRecordDeviceDrivers` is provided and also +defined as a function in iocshRegisterCommon.h. This uses dynamic symbol +lookup with `epicsFindSymbol()` to perform the same function as a generated +`*_registerRecordDeviceDriver()` function. This allows for an alternative +approach to dynamic loading of support modules without code generation. -This feature is not intended for use by IOCs constructed using the standard EPICS application -build process and booted from a startup script in an iocBoot subdirectory, although it might -work in some of those cases (the IOC's registerRecordDeviceDriver.cpp file is still required -to link everything into the executable). It also won't work with some static build -configurations or where the symbol table has been stripped from the executable. +This feature is not intended for use by IOCs constructed using the standard +EPICS application build process and booted from a startup script in an iocBoot +subdirectory, although it might work in some of those cases — the +generated registerRecordDeviceDriver.cpp file is normally required to link +everything referred to in the DBD file into the IOC's executable. It also +won't work with some static build configurations, or if the symbol table has +been stripped from the executable. ### Using a `{const:"string"}` to initialize an array of `DBF_CHAR` @@ -282,6 +343,8 @@ GNUmake added the directive `undefine` in version 3.82 to allow variables to be undefined. Support for this has been added to the EPICS Release file parser, so `undefine` can now be used in configure/RELEASE files to unset variables. +----- + ## EPICS Release 7.0.4.1 ### ARM Architecture Changes @@ -329,6 +392,8 @@ Bad character ' ' in record name "bad practice" if a record name begins with a minus, plus, left square bracket, or left curly bracket. +----- + ## EPICS Release 7.0.4 ### Bug fixes @@ -430,7 +495,7 @@ work with older Base releases. This would also be a good time to modify the device support to use the type-safe device support entry tables that were introduced in Base-3.16.2 -- see -[#type-safe-device-and-driver-support-tables](this entry below) for the +[this entry below](#type-safe-device-and-driver-support-tables) for the description of that change, which is also optional for now. Look at the aiRecord for example. Near the top of the generated `aiRecord.h` @@ -513,6 +578,8 @@ devLsiEtherIP = { }; ``` +----- + ## EPICS Release 7.0.3.1 **IMPORTANT NOTE:** *Some record types in this release will not be compatible @@ -706,6 +773,8 @@ necessary, all RTEMS targets should now link although the IOC won't be able to be used with the VME I/O on those systems (that we don't have VMEbus I/O support for in libCom). +----- + ## EPICS Release 7.0.3 ### `epicsTimeGetCurrent()` optimization @@ -726,6 +795,8 @@ This may result in slightly fewer, but larger frames being sent. Report NOBT as "precision" through the dbAccess API. This is not accessible through CA, but is planned to be used through QSRV. +----- + ## EPICS Release 7.0.2.2 ### Build System changes @@ -760,6 +831,8 @@ substantial than bug fixes. Turns out this is ~10x slower to query than `CLOCK_MONOTONIC`. +----- + ## EPICS Release 7.0.2.1 ### Linking shared libraries on macOS @@ -811,6 +884,8 @@ rewrite of the link address parser code in dbStaticLib. This release fixes that issue, although in some cases the output may be slightly different than it used to be. +----- + ## EPICS Release 7.0.2 ### Launchpad Bugs @@ -828,6 +903,8 @@ modules. The layout of the source files has not changed at all however, so the source code for libcom, ca and the database are still found separately under the module subdirectory. +----- + ## EPICS Release 7.0.1.1 ### Changed SIML failure behavior @@ -886,7 +963,11 @@ than is currently available, but as developers we generally much prefer to write code than documentation. Send questions to the tech-talk mailing list and we'll be happy to try and answer them! -## Changes between 3.16.1 and 3.16.2 +----- + +## Changes made between 3.16.1 and 3.16.2 + +### Launchpad Bugs The list of tracked bugs fixed in this release can be found on the [Launchpad Milestone page for EPICS Base 3.16.2](https://launchpad.net/epics-base/+milestone/3.16.2). @@ -1093,6 +1174,8 @@ array is made even larger; the previous array buffer was not being released correctly. See Launchpad [bug #1706703](https://bugs.launchpad.net/epics-base/+bug/1706703). +----- + ## Changes made between 3.16.0.1 and 3.16.1 ### IOC Database Support for 64-bit integers @@ -1529,6 +1612,7 @@ and then replace `(RECSUPFUN)` with `RECSUPFUN_CAST` when initializing the rset. Further changes might also be needed, e.g. to adapt `const`-ness of method parameters. +----- ## Changes made between 3.15.3 and 3.16.0.1 @@ -1637,6 +1721,8 @@ header and removed the need for dbScan.c to reach into the internals of its `CALLBACK` objects. +----- + # Changes incorporated from the 3.15 branch @@ -1648,6 +1734,7 @@ 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 @@ -1753,6 +1840,7 @@ don't provide it any more. If multiple IOCs were started at the same time, by systemd say, they could race to obtain the Channel Access TCP port number 5064. This issue has been fixed. +----- ## Changes made between 3.15.6 and 3.15.7 @@ -1896,6 +1984,8 @@ into the htmls directory. Thanks to Tony Pietryla. This displays the version numbers of EPICS Base and the CA protocol. +----- + ## Changes made between 3.15.5 and 3.15.6 ### Unsetting environment variables @@ -2121,6 +2211,8 @@ choice string cannot be parsed, the associated periodic scan thread will no longer be started by the IOC and a warning message will be displayed at iocInit time. The `scanppl` command will also flag the faulty menuScan value. +----- + ## Changes made between 3.15.4 and 3.15.5 ### dbStatic Library Speedup and Cleanup @@ -2252,6 +2344,8 @@ will be installed into the target bin directory, from where it can be copied into the appropriate systemd location and modified as necessary. Installation instructions are included as comments in the file. +----- + ## Changes made between 3.15.3 and 3.15.4 ### New string input device support "getenv" @@ -2357,6 +2451,8 @@ variable to a non-zero value before loading the file, like this: This was [Launchpad bug 541119](https://bugs.launchpad.net/bugs/541119). +----- + ## Changes from the 3.14 branch between 3.15.3 and 3.15.4 ### NTP Time Provider adjusts to OS tick rate changes diff --git a/documentation/ReleaseChecklist.html b/documentation/ReleaseChecklist.html index 117748b06..036f56a65 100644 --- a/documentation/ReleaseChecklist.html +++ b/documentation/ReleaseChecklist.html @@ -147,17 +147,17 @@ starting at Release Approval.

Tag the module in Git, using these tag conventions:
cd base-7.0
- git tag -m 'ANJ: Tagged for 7.0.4.1-rc1' R7.0.4.1-rc1 + git tag -m 'ANJ: Tagged for 7.0.5-rc1' R7.0.5-rc1
Note that submodules must not be tagged with the version used for the top-level, they each have their own separate version numbers @@ -171,11 +171,11 @@ starting at Release Approval.

files and directories that are only used for continuous integration:
cd base-7.0
- ./.tools/make-tar.sh R7.0.4.1-rc1 base-7.0.4.1-rc1.tar.gz base-7.0.4.1-rc1/ + ./.tools/make-tar.sh R7.0.5-rc1 base-7.0.5-rc1.tar.gz base-7.0.5-rc1/
Create a GPG signature file of the tarfile as follows:
- gpg --armor --sign --detach-sig base-7.0.4.1-rc1.tar.gz + gpg --armor --sign --detach-sig base-7.0.5-rc1.tar.gz
@@ -298,7 +298,7 @@ starting at Release Approval.

  • Tag the module:
    - git tag -m 'ANJ: Tag for EPICS 7.0.4.1' <module-version> + git tag -m 'ANJ: Tag for EPICS 7.0.5' <module-version>
  • @@ -355,7 +355,7 @@ starting at Release Approval.

    Tag the epics-base module in Git:
    cd base-7.0
    - git tag -m 'ANJ: Tagged for release' R7.0.4.1 + git tag -m 'ANJ: Tagged for release' R7.0.5

    Don't push these commits or the new tag to the Launchpad repository yet.

    @@ -387,12 +387,12 @@ starting at Release Approval.

    files and directories that are only used for continuous integration:
    cd base-7.0
    - ./.tools/make-tar.sh R7.0.4.1 ../base-7.0.4.1.tar.gz base-7.0.4.1/ + ./.tools/make-tar.sh R7.0.5 ../base-7.0.5.tar.gz base-7.0.5/
    Create a GPG signature file of the tarfile as follows:
    cd ..
    - gpg --armor --sign --detach-sig base-7.0.4.1.tar.gz + gpg --armor --sign --detach-sig base-7.0.5.tar.gz
    @@ -457,7 +457,7 @@ starting at Release Approval.

    Upload the tar file and its .asc signature file to the epics-controls web-server.
    - scp base-7.0.4.1.tar.gz base-7.0.4.1.tar.gz.asc epics-controls:download/base
    + scp base-7.0.5.tar.gz base-7.0.5.tar.gz.asc epics-controls:download/base
    diff --git a/documentation/mainpage.dox b/documentation/mainpage.dox index f13fdcf97..472f1d999 100644 --- a/documentation/mainpage.dox +++ b/documentation/mainpage.dox @@ -3,7 +3,6 @@ Documentation index -@ul @li @ref releasenotes @li @ref install @li @ref recordrefmanual diff --git a/modules/ca/src/perl/Makefile b/modules/ca/src/perl/Makefile index 532780d4c..05500ad05 100644 --- a/modules/ca/src/perl/Makefile +++ b/modules/ca/src/perl/Makefile @@ -38,6 +38,7 @@ ifeq ($(wildcard $(PERL_h)),) endif endif +ifneq (inc,$(strip $(MAKECMDGOALS))) ifeq ($(T_A),$(EPICS_HOST_ARCH)) # No cross-builds (wrong Perl!) ifeq ($(strip $(XSUBPP)),) $(warning Perl's xsubpp program was not found.) @@ -62,6 +63,7 @@ endif endif endif endif +endif Cap5_SRCS = Cap5.xs Cap5_LIBS = ca Com diff --git a/modules/database/src/ioc/as/asIocRegister.c b/modules/database/src/ioc/as/asIocRegister.c index 200da3e00..8efb4c9eb 100644 --- a/modules/database/src/ioc/as/asIocRegister.c +++ b/modules/database/src/ioc/as/asIocRegister.c @@ -120,7 +120,9 @@ static void ascarCallFunc(const iocshArgBuf *args) } /* asDumpHash */ -static const iocshFuncDef asDumpHashFuncDef = {"asDumpHash",0,0}; +static const iocshFuncDef asDumpHashFuncDef = {"asDumpHash",0,0, + "Show the contents of the hash table used " + "to locate UAGs and HAGs.\n"}; static void asDumpHashCallFunc(const iocshArgBuf *args) { asDumpHash(); diff --git a/modules/database/src/ioc/bpt/Makefile b/modules/database/src/ioc/bpt/Makefile index ea1db1f14..a383d0864 100644 --- a/modules/database/src/ioc/bpt/Makefile +++ b/modules/database/src/ioc/bpt/Makefile @@ -19,7 +19,10 @@ BPT_DBD += bptTypeJdegC.dbd BPT_DBD += bptTypeJdegF.dbd BPT_DBD += bptTypeKdegC.dbd BPT_DBD += bptTypeKdegF.dbd + +ifneq (inc,$(strip $(MAKECMDGOALS))) DBD += $(BPT_DBD) +endif PROD_HOST += makeBpt diff --git a/modules/database/src/ioc/db/dbAccess.c b/modules/database/src/ioc/db/dbAccess.c index d7e5d0890..bac208f02 100644 --- a/modules/database/src/ioc/db/dbAccess.c +++ b/modules/database/src/ioc/db/dbAccess.c @@ -339,7 +339,7 @@ static void getOptions(DBADDR *paddr, char **poriginal, long *options, dbCommon *pcommon; char *pbuffer = *poriginal; - if (!pfl || pfl->type == dbfl_type_rec) + if (!pfl) field_type = paddr->field_type; else field_type = pfl->field_type; @@ -349,7 +349,7 @@ static void getOptions(DBADDR *paddr, char **poriginal, long *options, if( (*options) & DBR_STATUS ) { unsigned short *pushort = (unsigned short *)pbuffer; - if (!pfl || pfl->type == dbfl_type_rec) { + if (!pfl) { *pushort++ = pcommon->stat; *pushort++ = pcommon->sevr; } else { @@ -383,7 +383,7 @@ static void getOptions(DBADDR *paddr, char **poriginal, long *options, if( (*options) & DBR_TIME ) { epicsUInt32 *ptime = (epicsUInt32 *)pbuffer; - if (!pfl || pfl->type == dbfl_type_rec) { + if (!pfl) { *ptime++ = pcommon->time.secPastEpoch; *ptime++ = pcommon->time.nsec; } else { @@ -904,22 +904,23 @@ long dbGet(DBADDR *paddr, short dbrType, if (nRequest && *nRequest == 0) return 0; - if (!pfl || pfl->type == dbfl_type_rec) { + if (!pfl) { field_type = paddr->field_type; no_elements = capacity = paddr->no_elements; - - /* Update field info from record - * may modify paddr->pfield - */ - if (paddr->pfldDes->special == SPC_DBADDR && - (prset = dbGetRset(paddr)) && - prset->get_array_info) { - status = prset->get_array_info(paddr, &no_elements, &offset); - } else - offset = 0; } else { field_type = pfl->field_type; no_elements = capacity = pfl->no_elements; + } + + /* Update field info from record (if neccessary); + * may modify paddr->pfield. + */ + if (!dbfl_has_copy(pfl) && + paddr->pfldDes->special == SPC_DBADDR && + (prset = dbGetRset(paddr)) && + prset->get_array_info) { + status = prset->get_array_info(paddr, &no_elements, &offset); + } else { offset = 0; } @@ -951,7 +952,7 @@ long dbGet(DBADDR *paddr, short dbrType, goto done; } - if (!pfl || pfl->type == dbfl_type_rec) { + if (!dbfl_has_copy(pfl)) { status = dbFastGetConvertRoutine[field_type][dbrType] (paddr->pfield, pbuf, paddr); } else { @@ -964,11 +965,9 @@ long dbGet(DBADDR *paddr, short dbrType, localAddr.field_type = pfl->field_type; localAddr.field_size = pfl->field_size; + /* not used by dbFastConvert: */ localAddr.no_elements = pfl->no_elements; - if (pfl->type == dbfl_type_val) - localAddr.pfield = (char *) &pfl->u.v.field; - else - localAddr.pfield = (char *) pfl->u.r.field; + localAddr.pfield = dbfl_pfield(pfl); status = dbFastGetConvertRoutine[field_type][dbrType] (localAddr.pfield, pbuf, &localAddr); } @@ -979,6 +978,8 @@ long dbGet(DBADDR *paddr, short dbrType, if (nRequest) { if (no_elements < *nRequest) *nRequest = no_elements; + if (capacity < *nRequest) + *nRequest = capacity; n = *nRequest; } else { n = 1; @@ -995,8 +996,8 @@ long dbGet(DBADDR *paddr, short dbrType, } /* convert data into the caller's buffer */ if (n <= 0) { - ;/*do nothing*/ - } else if (!pfl || pfl->type == dbfl_type_rec) { + ; /*do nothing */ + } else if (!dbfl_has_copy(pfl)) { status = convert(paddr, pbuf, n, capacity, offset); } else { DBADDR localAddr = *paddr; /* Structure copy */ @@ -1008,11 +1009,9 @@ long dbGet(DBADDR *paddr, short dbrType, localAddr.field_type = pfl->field_type; localAddr.field_size = pfl->field_size; + /* not used by dbConvert, it uses the passed capacity instead: */ localAddr.no_elements = pfl->no_elements; - if (pfl->type == dbfl_type_val) - localAddr.pfield = (char *) &pfl->u.v.field; - else - localAddr.pfield = (char *) pfl->u.r.field; + localAddr.pfield = dbfl_pfield(pfl); status = convert(&localAddr, pbuf, n, capacity, offset); } diff --git a/modules/database/src/ioc/db/dbChannel.c b/modules/database/src/ioc/db/dbChannel.c index b6f53f797..c71d8426f 100644 --- a/modules/database/src/ioc/db/dbChannel.c +++ b/modules/database/src/ioc/db/dbChannel.c @@ -52,14 +52,12 @@ typedef struct parseContext { static void *dbChannelFreeList; static void *chFilterFreeList; -static void *dbchStringFreeList; void dbChannelExit(void) { freeListCleanup(dbChannelFreeList); freeListCleanup(chFilterFreeList); - freeListCleanup(dbchStringFreeList); - dbChannelFreeList = chFilterFreeList = dbchStringFreeList = NULL; + dbChannelFreeList = chFilterFreeList = NULL; } void dbChannelInit (void) @@ -69,7 +67,6 @@ void dbChannelInit (void) freeListInitPvt(&dbChannelFreeList, sizeof(dbChannel), 128); freeListInitPvt(&chFilterFreeList, sizeof(chFilter), 64); - freeListInitPvt(&dbchStringFreeList, sizeof(epicsOldString), 128); db_init_event_freelists(); } @@ -449,28 +446,6 @@ static long parseArrayRange(dbChannel* chan, const char *pname, const char **ppn return status; } -/* Stolen from dbAccess.c: */ -static short mapDBFToDBR[DBF_NTYPES] = - { - /* DBF_STRING => */DBR_STRING, - /* DBF_CHAR => */DBR_CHAR, - /* DBF_UCHAR => */DBR_UCHAR, - /* DBF_SHORT => */DBR_SHORT, - /* DBF_USHORT => */DBR_USHORT, - /* DBF_LONG => */DBR_LONG, - /* DBF_ULONG => */DBR_ULONG, - /* DBF_INT64 => */DBR_INT64, - /* DBF_UINT64 => */DBR_UINT64, - /* DBF_FLOAT => */DBR_FLOAT, - /* DBF_DOUBLE => */DBR_DOUBLE, - /* DBF_ENUM, => */DBR_ENUM, - /* DBF_MENU, => */DBR_ENUM, - /* DBF_DEVICE => */DBR_ENUM, - /* DBF_INLINK => */DBR_STRING, - /* DBF_OUTLINK => */DBR_STRING, - /* DBF_FWDLINK => */DBR_STRING, - /* DBF_NOACCESS => */DBR_NOACCESS }; - dbChannel * dbChannelCreate(const char *name) { const char *pname = name; @@ -743,37 +718,24 @@ void dbChannelDelete(dbChannel *chan) freeListFree(dbChannelFreeList, chan); } -static void freeArray(db_field_log *pfl) { - if (pfl->field_type == DBF_STRING && pfl->no_elements == 1) { - freeListFree(dbchStringFreeList, pfl->u.r.field); - } else { - free(pfl->u.r.field); - } -} - -void dbChannelMakeArrayCopy(void *pvt, db_field_log *pfl, dbChannel *chan) +/* + * Helper function to adjust no_elements, offset, and pfield + * when copying an array from a record. + */ +void dbChannelGetArrayInfo(dbChannel *chan, + void **pfield, long *no_elements, long *offset) { - void *p; - struct dbCommon *prec = dbChannelRecord(chan); - - if (pfl->type != dbfl_type_rec) return; - - pfl->type = dbfl_type_ref; - pfl->stat = prec->stat; - pfl->sevr = prec->sevr; - pfl->time = prec->time; - pfl->field_type = chan->addr.field_type; - pfl->no_elements = chan->addr.no_elements; - pfl->field_size = chan->addr.field_size; - pfl->u.r.dtor = freeArray; - pfl->u.r.pvt = pvt; - if (pfl->field_type == DBF_STRING && pfl->no_elements == 1) { - p = freeListCalloc(dbchStringFreeList); - } else { - p = calloc(pfl->no_elements, pfl->field_size); + rset *prset; + if (dbChannelSpecial(chan) == SPC_DBADDR && + (prset = dbGetRset(&chan->addr)) && + prset->get_array_info) + { + void *pfieldsave = dbChannelField(chan); + /* it is expected that this call always succeeds */ + prset->get_array_info(&chan->addr, no_elements, offset); + *pfield = dbChannelField(chan); + dbChannelField(chan) = pfieldsave; } - if (p) dbGet(&chan->addr, mapDBFToDBR[pfl->field_type], p, NULL, &pfl->no_elements, NULL); - pfl->u.r.field = p; } /* FIXME: Do these belong in a different file? */ diff --git a/modules/database/src/ioc/db/dbChannel.h b/modules/database/src/ioc/db/dbChannel.h index 1ca02bbb6..ec86e9e28 100644 --- a/modules/database/src/ioc/db/dbChannel.h +++ b/modules/database/src/ioc/db/dbChannel.h @@ -65,8 +65,8 @@ typedef struct dbChannel { /* Prototype for the channel event function that is called in filter stacks * * When invoked the scan lock for the record associated with 'chan' _may_ be locked. - * If pLog->type==dbfl_type_rec then dbScanLock() must be called before copying - * data out of the associated record. + * Unless dbfl_has_copy(pLog), it must call dbScanLock before accessing the data, + * as this indicates the data is still owned by the record. * * This function has ownership of the field log pLog, if it wishes to discard * this update it should free the field log with db_delete_field_log() and @@ -225,7 +225,8 @@ DBCORE_API void dbRegisterFilter(const char *key, const chFilterIf *fif, void *p DBCORE_API db_field_log* dbChannelRunPreChain(dbChannel *chan, db_field_log *pLogIn); DBCORE_API db_field_log* dbChannelRunPostChain(dbChannel *chan, db_field_log *pLogIn); DBCORE_API const chFilterPlugin * dbFindFilter(const char *key, size_t len); -DBCORE_API void dbChannelMakeArrayCopy(void *pvt, db_field_log *pfl, dbChannel *chan); +DBCORE_API void dbChannelGetArrayInfo(dbChannel *chan, + void **pfield, long *no_elements, long *offset); #ifdef __cplusplus } diff --git a/modules/database/src/ioc/db/dbEvent.c b/modules/database/src/ioc/db/dbEvent.c index 437cb6d5d..1db01f2cd 100644 --- a/modules/database/src/ioc/db/dbEvent.c +++ b/modules/database/src/ioc/db/dbEvent.c @@ -668,27 +668,21 @@ int db_post_extra_labor (dbEventCtx ctx) return DB_EVENT_OK; } -/* - * DB_CREATE_EVENT_LOG() - * - * NOTE: This assumes that the db scan lock is already applied - * (as it copies data from the record) - */ -db_field_log* db_create_event_log (struct evSubscrip *pevent) +static db_field_log* db_create_field_log (struct dbChannel *chan, int use_val) { db_field_log *pLog = (db_field_log *) freeListCalloc(dbevFieldLogFreeList); if (pLog) { - struct dbChannel *chan = pevent->chan; struct dbCommon *prec = dbChannelRecord(chan); - pLog->ctx = dbfl_context_event; - if (pevent->useValque) { + pLog->stat = prec->stat; + pLog->sevr = prec->sevr; + pLog->time = prec->time; + pLog->field_type = dbChannelFieldType(chan); + pLog->field_size = dbChannelFieldSize(chan); + pLog->no_elements = dbChannelElements(chan); + + if (use_val) { pLog->type = dbfl_type_val; - pLog->stat = prec->stat; - pLog->sevr = prec->sevr; - pLog->time = prec->time; - pLog->field_type = dbChannelFieldType(chan); - pLog->no_elements = dbChannelElements(chan); /* * use memcpy to avoid a bus error on * union copy of char in the db at an odd @@ -698,23 +692,46 @@ db_field_log* db_create_event_log (struct evSubscrip *pevent) dbChannelField(chan), dbChannelFieldSize(chan)); } else { - pLog->type = dbfl_type_rec; + pLog->type = dbfl_type_ref; + + /* don't make a copy yet, just reference the field value */ + pLog->u.r.field = dbChannelField(chan); + /* indicate field value still owned by record */ + pLog->u.r.dtor = NULL; + /* no private data yet, may be set by a filter */ + pLog->u.r.pvt = NULL; } } return pLog; } +/* + * DB_CREATE_EVENT_LOG() + * + * NOTE: This assumes that the db scan lock is already applied + * (as it calls rset->get_array_info) + */ +db_field_log* db_create_event_log (struct evSubscrip *pevent) +{ + db_field_log *pLog = db_create_field_log(pevent->chan, pevent->useValque); + if (pLog) { + pLog->ctx = dbfl_context_event; + } + return pLog; +} + /* * DB_CREATE_READ_LOG() * */ db_field_log* db_create_read_log (struct dbChannel *chan) { - db_field_log *pLog = (db_field_log *) freeListCalloc(dbevFieldLogFreeList); - + db_field_log *pLog = db_create_field_log(chan, + dbChannelElements(chan) == 1 && + dbChannelSpecial(chan) != SPC_DBADDR && + dbChannelFieldSize(chan) <= sizeof(union native_value)); if (pLog) { pLog->ctx = dbfl_context_read; - pLog->type = dbfl_type_rec; } return pLog; } @@ -737,20 +754,6 @@ static void db_queue_event_log (evSubscrip *pevent, db_field_log *pLog) LOCKEVQUE (ev_que); - /* - * if we have an event on the queue and both the last - * event on the queue and the current event are emtpy - * (i.e. of type dbfl_type_rec), simply ignore duplicate - * events (saving empty events serves no purpose) - */ - if (pevent->npend > 0u && - (*pevent->pLastLog)->type == dbfl_type_rec && - pLog->type == dbfl_type_rec) { - db_delete_field_log(pLog); - UNLOCKEVQUE (ev_que); - return; - } - /* * add to task local event que */ diff --git a/modules/database/src/ioc/db/dbExtractArray.c b/modules/database/src/ioc/db/dbExtractArray.c index a7dcf4d0c..197e66c3a 100644 --- a/modules/database/src/ioc/db/dbExtractArray.c +++ b/modules/database/src/ioc/db/dbExtractArray.c @@ -14,11 +14,12 @@ /* * Author: Ralph Lange * - * based on dbConvert.c + * based on dbConvert.c, see copyNoConvert * written by: Bob Dalesio, Marty Kraimer */ #include +#include #include "epicsTypes.h" @@ -26,61 +27,30 @@ #include "dbAddr.h" #include "dbExtractArray.h" -void dbExtractArrayFromRec(const dbAddr *paddr, void *pto, - long nRequest, long no_elements, long offset, long increment) +void dbExtractArray(const void *pfrom, void *pto, short field_size, + long nRequest, long no_elements, long offset, long increment) { char *pdst = (char *) pto; - char *psrc = (char *) paddr->pfield; - long nUpperPart; - int i; - short srcSize = paddr->field_size; - short dstSize = srcSize; - char isString = (paddr->field_type == DBF_STRING); + const char *psrc = (char *) pfrom; - if (nRequest > no_elements) nRequest = no_elements; - if (isString && srcSize > MAX_STRING_SIZE) dstSize = MAX_STRING_SIZE; - - if (increment == 1 && dstSize == srcSize) { - nUpperPart = nRequest < no_elements - offset ? nRequest : no_elements - offset; - memcpy(pdst, &psrc[offset * srcSize], dstSize * nUpperPart); - if (nRequest > nUpperPart) - memcpy(&pdst[dstSize * nUpperPart], psrc, dstSize * (nRequest - nUpperPart)); - if (isString) - for (i = 1; i <= nRequest; i++) - pdst[dstSize*i-1] = '\0'; - } else { - for (; nRequest > 0; nRequest--, pdst += dstSize, offset += increment) { - offset %= no_elements; - memcpy(pdst, &psrc[offset*srcSize], dstSize); - if (isString) pdst[dstSize-1] = '\0'; - } - } -} - -void dbExtractArrayFromBuf(const void *pfrom, void *pto, - short field_size, short field_type, - long nRequest, long no_elements, long offset, long increment) -{ - char *pdst = (char *) pto; - char *psrc = (char *) pfrom; - int i; - short srcSize = field_size; - short dstSize = srcSize; - char isString = (field_type == DBF_STRING); - - if (nRequest > no_elements) nRequest = no_elements; - if (offset > no_elements - 1) offset = no_elements - 1; - if (isString && dstSize >= MAX_STRING_SIZE) dstSize = MAX_STRING_SIZE - 1; + /* assert preconditions */ + assert(nRequest >= 0); + assert(no_elements >= 0); + assert(increment > 0); + assert(0 <= offset); + assert(offset < no_elements); if (increment == 1) { - memcpy(pdst, &psrc[offset * srcSize], dstSize * nRequest); - if (isString) - for (i = 1; i <= nRequest; i++) - pdst[dstSize*i] = '\0'; + long nUpperPart = + nRequest < no_elements - offset ? nRequest : no_elements - offset; + memcpy(pdst, psrc + (offset * field_size), field_size * nUpperPart); + if (nRequest > nUpperPart) + memcpy(pdst + (field_size * nUpperPart), psrc, + field_size * (nRequest - nUpperPart)); } else { - for (; nRequest > 0; nRequest--, pdst += srcSize, offset += increment) { - memcpy(pdst, &psrc[offset*srcSize], dstSize); - if (isString) pdst[dstSize] = '\0'; + for (; nRequest > 0; nRequest--, pdst += field_size, offset += increment) { + offset %= no_elements; + memcpy(pdst, psrc + (offset * field_size), field_size); } } } diff --git a/modules/database/src/ioc/db/dbExtractArray.h b/modules/database/src/ioc/db/dbExtractArray.h index b44bd15fc..9755ac570 100644 --- a/modules/database/src/ioc/db/dbExtractArray.h +++ b/modules/database/src/ioc/db/dbExtractArray.h @@ -22,11 +22,36 @@ extern "C" { #endif -epicsShareFunc void dbExtractArrayFromRec(const DBADDR *paddr, void *pto, - long nRequest, long no_elements, long offset, long increment); -epicsShareFunc void dbExtractArrayFromBuf(const void *pfrom, void *pto, - short field_size, short field_type, - long nRequest, long no_elements, long offset, long increment); +/** @brief Make a copy of parts of an array. + * + * The source array may or may not be a record field. + * + * The increment parameter is used to support array filters; it + * means: copy only every increment'th element, starting at offset. + * + * The offset and no_elements parameters are used to support the + * circular buffer feature of record fields: elements before offset + * are treated as if they came right after no_elements. + * + * This function does not do any conversion on the array elements. + * + * Preconditions: + * nRequest >= 0, no_elements >= 0, increment > 0 + * 0 <= offset < no_elements + * pto points to a buffer with at least field_size*nRequest bytes + * pfrom points to a buffer with exactly field_size*no_elements bytes + * + * @param pfrom Pointer to source array. + * @param pto Pointer to target array. + * @param field_size Size of an array element. + * @param nRequest Number of elements to copy. + * @param no_elements Number of elements in source array. + * @param offset Wrap-around point in source array. + * @param increment Copy only every increment'th element. + */ +epicsShareFunc void dbExtractArray(const void *pfrom, void *pto, + short field_size, long nRequest, long no_elements, long offset, + long increment); #ifdef __cplusplus } diff --git a/modules/database/src/ioc/db/dbUnitTest.c b/modules/database/src/ioc/db/dbUnitTest.c index 65c817bbb..0b40d929c 100644 --- a/modules/database/src/ioc/db/dbUnitTest.c +++ b/modules/database/src/ioc/db/dbUnitTest.c @@ -112,12 +112,13 @@ union anybuf { long testdbVPutField(const char* pv, short dbrType, va_list ap) { - DBADDR addr; + dbChannel *chan = dbChannelCreate(pv); union anybuf pod; + long ret = S_dbLib_recNotFound; - if (dbNameToAddr(pv, &addr)) { - testFail("Missing PV \"%s\"", pv); - return S_dbLib_recNotFound; + if(!chan || (ret=dbChannelOpen(chan))) { + testFail("Channel error (%p, %ld) : %s", chan, ret, pv); + goto done; } switch(dbrType) { @@ -125,14 +126,18 @@ long testdbVPutField(const char* pv, short dbrType, va_list ap) const char *uarg = va_arg(ap,char*); strncpy(pod.valStr, uarg, sizeof(pod.valStr)); pod.valStr[sizeof(pod.valStr)-1] = '\0'; - return dbPutField(&addr, dbrType, pod.valStr, 1); + ret = dbChannelPutField(chan, dbrType, pod.valStr, 1); + break; } /* The Type parameter takes into consideration * the C language rules for promotion of argument types * in variadic functions. */ -#define OP(DBR,Type,mem) case DBR: {pod.val.mem = va_arg(ap,Type); break;} +#define OP(DBR,Type,mem) case DBR: \ + pod.val.mem = va_arg(ap,Type); \ + ret = dbChannelPutField(chan, dbrType, pod.bytes, 1); \ + break; OP(DBR_CHAR, int, int8); OP(DBR_UCHAR, int, uInt8); OP(DBR_SHORT, int, int16); @@ -147,11 +152,15 @@ long testdbVPutField(const char* pv, short dbrType, va_list ap) #undef OP default: testFail("invalid DBR: dbPutField(\"%s\", %d, ...)", - addr.precord->name, dbrType); - return S_db_badDbrtype; + dbChannelName(chan), dbrType); + ret = S_db_badDbrtype; + break; } - return dbPutField(&addr, dbrType, pod.bytes, 1); +done: + if(chan) + dbChannelDelete(chan); + return ret; } void testdbPutFieldOk(const char* pv, int dbrType, ...) @@ -190,23 +199,35 @@ void testdbGetFieldEqual(const char* pv, int dbrType, ...) void testdbVGetFieldEqual(const char* pv, short dbrType, va_list ap) { - DBADDR addr; + dbChannel *chan = dbChannelCreate(pv); + db_field_log *pfl = NULL; long nReq = 1; union anybuf pod; - long status; + long status = S_dbLib_recNotFound; - if(dbNameToAddr(pv, &addr)) { - testFail("Missing PV \"%s\"", pv); - return; + if(!chan || (status=dbChannelOpen(chan))) { + testFail("Channel error (%p, %ld) : %s", chan, status, pv); + goto done; } - status = dbGetField(&addr, dbrType, pod.bytes, NULL, &nReq, NULL); + if(ellCount(&chan->filters)) { + pfl = db_create_read_log(chan); + if (!pfl) { + testFail("can't db_create_read_log w/ %s", pv); + goto done; + } + + pfl = dbChannelRunPreChain(chan, pfl); + pfl = dbChannelRunPostChain(chan, pfl); + } + + status = dbChannelGetField(chan, dbrType, pod.bytes, NULL, &nReq, pfl); if (status) { testFail("dbGetField(\"%s\", %d, ...) -> %#lx (%s)", pv, dbrType, status, errSymMsg(status)); - return; + goto done; } else if(nReq==0) { testFail("dbGetField(\"%s\", %d, ...) -> zero length", pv, dbrType); - return; + goto done; } switch(dbrType) { @@ -236,35 +257,56 @@ void testdbVGetFieldEqual(const char* pv, short dbrType, va_list ap) default: testFail("dbGetField(\"%s\", %d) -> unsupported dbf", pv, dbrType); } + +done: + db_delete_field_log(pfl); + if(chan) + dbChannelDelete(chan); } void testdbPutArrFieldOk(const char* pv, short dbrType, unsigned long count, const void *pbuf) { - DBADDR addr; + dbChannel *chan = dbChannelCreate(pv); long status; - if (dbNameToAddr(pv, &addr)) { - testFail("Missing PV \"%s\"", pv); - return; + if(!chan || (status=dbChannelOpen(chan))) { + testFail("Channel error (%p, %ld) : %s", chan, status, pv); + goto done; } - status = dbPutField(&addr, dbrType, pbuf, count); + status = dbChannelPutField(chan, dbrType, pbuf, count); testOk(status==0, "dbPutField(\"%s\", dbr=%d, count=%lu, ...) -> %ld", pv, dbrType, count, status); + +done: + if(chan) + dbChannelDelete(chan); } void testdbGetArrFieldEqual(const char* pv, short dbfType, long nRequest, unsigned long cnt, const void *pbufraw) { - DBADDR addr; + dbChannel *chan = dbChannelCreate(pv); + db_field_log *pfl = NULL; const long vSize = dbValueSize(dbfType); const long nStore = vSize * nRequest; - long status; + long status = S_dbLib_recNotFound; char *gbuf, *gstore; const char *pbuf = pbufraw; - if(dbNameToAddr(pv, &addr)) { - testFail("Missing PV \"%s\"", pv); - return; + if(!chan || (status=dbChannelOpen(chan))) { + testFail("Channel error (%p, %ld) : %s", chan, status, pv); + goto done; + } + + if(ellCount(&chan->filters)) { + pfl = db_create_read_log(chan); + if (!pfl) { + testFail("can't db_create_read_log w/ %s", pv); + goto done; + } + + pfl = dbChannelRunPreChain(chan, pfl); + pfl = dbChannelRunPostChain(chan, pfl); } gbuf = gstore = malloc(nStore); @@ -273,7 +315,7 @@ void testdbGetArrFieldEqual(const char* pv, short dbfType, long nRequest, unsign return; } - status = dbGetField(&addr, dbfType, gbuf, NULL, &nRequest, NULL); + status = dbChannelGetField(chan, dbfType, gbuf, NULL, &nRequest, pfl); if (status) { testFail("dbGetField(\"%s\", %d, ...) -> %#lx", pv, dbfType, status); @@ -318,7 +360,10 @@ void testdbGetArrFieldEqual(const char* pv, short dbfType, long nRequest, unsign testOk(match, "dbGetField(\"%s\", dbrType=%d, nRequest=%ld ...) match", pv, dbfType, nRequest); } +done: free(gstore); + if(chan) + dbChannelDelete(chan); } dbCommon* testdbRecordPtr(const char* pv) diff --git a/modules/database/src/ioc/db/db_field_log.h b/modules/database/src/ioc/db/db_field_log.h index 5a40f5fad..e517d529f 100644 --- a/modules/database/src/ioc/db/db_field_log.h +++ b/modules/database/src/ioc/db/db_field_log.h @@ -57,20 +57,31 @@ union native_value { struct db_field_log; typedef void (dbfl_freeFunc)(struct db_field_log *pfl); -/* Types of db_field_log: rec = use record, val = val inside, ref = reference inside */ +/* + * A db_field_log has one of two types: + * + * dbfl_type_ref - Reference to value + * Used for variable size (array) data types. Meta-data + * is stored in the field log, but value data is stored externally. + * Only the dbfl_ref side of the data union is valid. + * + * dbfl_type_val - Internal value + * Used to store small scalar data. Meta-data and value are + * present in this structure and no external references are used. + * Only the dbfl_val side of the data union is valid. + */ typedef enum dbfl_type { - dbfl_type_rec = 0, dbfl_type_val, dbfl_type_ref } dbfl_type; /* Context of db_field_log: event = subscription update, read = read reply */ typedef enum dbfl_context { - dbfl_context_read = 0, + dbfl_context_read, dbfl_context_event } dbfl_context; -#define dbflTypeStr(t) (t==dbfl_type_val?"val":t==dbfl_type_rec?"rec":"ref") +#define dbflTypeStr(t) (t==dbfl_type_val?"val":"ref") struct dbfl_val { union native_value field; /* Field value */ @@ -82,6 +93,8 @@ struct dbfl_val { * db_delete_field_log(). Any code which changes a dbfl_type_ref * field log to another type, or to reference different data, * must explicitly call the dtor function. + * If the dtor is NULL and no_elements > 0, then this means the array + * data is still owned by a record. See the macro dbfl_has_copy below. */ struct dbfl_ref { dbfl_freeFunc *dtor; /* Callback to free filter-allocated resources */ @@ -89,8 +102,11 @@ struct dbfl_ref { void *field; /* Field value */ }; +/* + * Note that field_size may be larger than MAX_STRING_SIZE. + */ typedef struct db_field_log { - unsigned int type:2; /* type (union) selector */ + unsigned int type:1; /* type (union) selector */ /* ctx is used for all types */ unsigned int ctx:1; /* context (operation type) */ /* the following are used for value and reference types */ @@ -98,8 +114,8 @@ typedef struct db_field_log { unsigned short stat; /* Alarm Status */ unsigned short sevr; /* Alarm Severity */ short field_type; /* DBF type of data */ - short field_size; /* Data size */ - long no_elements; /* No of array elements */ + short field_size; /* Size of a single element */ + long no_elements; /* No of valid array elements */ union { struct dbfl_val v; struct dbfl_ref r; @@ -107,27 +123,19 @@ typedef struct db_field_log { } db_field_log; /* - * A db_field_log will in one of three types: - * - * dbfl_type_rec - Reference to record - * The field log stores no data itself. Data must instead be taken - * via the dbChannel* which must always be provided when along - * with the field log. - * For this type only the 'type' and 'ctx' members are used. - * - * dbfl_type_ref - Reference to outside value - * Used for variable size (array) data types. Meta-data - * is stored in the field log, but value data is stored externally - * (see struct dbfl_ref). - * For this type all meta-data members are used. The dbfl_ref side of the - * data union is used. - * - * dbfl_type_val - Internal value - * Used to store small scalar data. Meta-data and value are - * present in this structure and no external references are used. - * For this type all meta-data members are used. The dbfl_val side of the - * data union is used. + * Whether a db_field_log* owns the field data. If this is the case, then the + * db_field_log is fully decoupled from the record: there is no need to lock + * the record when accessing the data, nor to call any rset methods (like + * get_array_info) because this has already been done when we made the copy. A + * special case here is that of no (remaining) data (i.e. no_elements==0). In + * this case, making a copy is redundant, so we have no dtor. But conceptually + * the db_field_log still owns the (empty) data. */ +#define dbfl_has_copy(p)\ + ((p) && ((p)->type==dbfl_type_val || (p)->u.r.dtor || (p)->no_elements==0)) + +#define dbfl_pfield(p)\ + ((p)->type==dbfl_type_val ? &p->u.v.field : p->u.r.field) #ifdef __cplusplus } diff --git a/modules/database/src/ioc/dbStatic/dbStaticIocRegister.c b/modules/database/src/ioc/dbStatic/dbStaticIocRegister.c index 1a1960636..2f33ccff4 100644 --- a/modules/database/src/ioc/dbStatic/dbStaticIocRegister.c +++ b/modules/database/src/ioc/dbStatic/dbStaticIocRegister.c @@ -34,8 +34,10 @@ static void dbDumpPathCallFunc(const iocshArgBuf *args) static const iocshArg dbDumpRecordArg2 = { "interest level",iocshArgInt}; static const iocshArg * const dbDumpRecordArgs[] = {&argPdbbase, &argRecType, &dbDumpRecordArg2}; -static const iocshFuncDef dbDumpRecordFuncDef = - {"dbDumpRecord",3,dbDumpRecordArgs}; +static const iocshFuncDef dbDumpRecordFuncDef = {"dbDumpRecord",3,dbDumpRecordArgs, + "Dump information about the recordTypeName with 'interest level' details.\n" + "Example: dbDumpRecord ai 2\n" + "If last argument(s) are missing, dump all recordType information in the database.\n"}; static void dbDumpRecordCallFunc(const iocshArgBuf *args) { dbDumpRecord(*iocshPpdbbase,args[1].sval,args[2].ival); @@ -45,7 +47,10 @@ static void dbDumpRecordCallFunc(const iocshArgBuf *args) static const iocshArg dbDumpMenuArg1 = { "menuName",iocshArgString}; static const iocshArg * const dbDumpMenuArgs[] = { &argPdbbase, &dbDumpMenuArg1}; -static const iocshFuncDef dbDumpMenuFuncDef = {"dbDumpMenu",2,dbDumpMenuArgs}; +static const iocshFuncDef dbDumpMenuFuncDef = {"dbDumpMenu",2,dbDumpMenuArgs, + "Dump information about the available menuNames and choices defined withing each menuName.\n" + "Example: dbDumpMenu pdbbase menuAlarmStat \n" + "If last argument(s) are missing, dump all menuNames information in the database.\n"}; static void dbDumpMenuCallFunc(const iocshArgBuf *args) { dbDumpMenu(*iocshPpdbbase,args[1].sval); @@ -54,8 +59,10 @@ static void dbDumpMenuCallFunc(const iocshArgBuf *args) /* dbDumpRecordType */ static const iocshArg * const dbDumpRecordTypeArgs[] = {&argPdbbase, &argRecType}; -static const iocshFuncDef dbDumpRecordTypeFuncDef = - {"dbDumpRecordType",2,dbDumpRecordTypeArgs}; +static const iocshFuncDef dbDumpRecordTypeFuncDef = {"dbDumpRecordType",2,dbDumpRecordTypeArgs, + "Dump information about available fields in the recortTypeName sorted by index and name.\n" + "Example: dbDumpRecordType pdbbase calcout\n" + "If last argument(s) are missing, dump fields information for all records in the database.\n"}; static void dbDumpRecordTypeCallFunc(const iocshArgBuf *args) { dbDumpRecordType(*iocshPpdbbase,args[1].sval); @@ -65,7 +72,11 @@ static void dbDumpRecordTypeCallFunc(const iocshArgBuf *args) static const iocshArg dbDumpFieldArg2 = { "fieldName",iocshArgString}; static const iocshArg * const dbDumpFieldArgs[] = {&argPdbbase, &argRecType,&dbDumpFieldArg2}; -static const iocshFuncDef dbDumpFieldFuncDef = {"dbDumpField",3,dbDumpFieldArgs}; +static const iocshFuncDef dbDumpFieldFuncDef = {"dbDumpField",3,dbDumpFieldArgs, + "Dump information about the fieldName in the recordTypeName.\n" + "Example: dbDumpField pdbbase calcout A\n" + "If last argument(s) are missing, dump information\n" + "about all fieldName in all recordTypeName in the database.\n"}; static void dbDumpFieldCallFunc(const iocshArgBuf *args) { dbDumpField(*iocshPpdbbase,args[1].sval,args[2].sval); @@ -74,7 +85,11 @@ static void dbDumpFieldCallFunc(const iocshArgBuf *args) /* dbDumpDevice */ static const iocshArg * const dbDumpDeviceArgs[] = { &argPdbbase, &argRecType}; -static const iocshFuncDef dbDumpDeviceFuncDef = {"dbDumpDevice",2,dbDumpDeviceArgs}; +static const iocshFuncDef dbDumpDeviceFuncDef = {"dbDumpDevice",2,dbDumpDeviceArgs, + "Dump device support information for the recordTypeName.\n" + "Example: dbDumpDevice pdbbase ai\n" + "If last argument(s) are missing, dump device support\n" + "information for all records in the database.\n"}; static void dbDumpDeviceCallFunc(const iocshArgBuf *args) { dbDumpDevice(*iocshPpdbbase,args[1].sval); @@ -82,7 +97,9 @@ static void dbDumpDeviceCallFunc(const iocshArgBuf *args) /* dbDumpDriver */ static const iocshArg * const dbDumpDriverArgs[] = { &argPdbbase}; -static const iocshFuncDef dbDumpDriverFuncDef = {"dbDumpDriver",1,dbDumpDriverArgs}; +static const iocshFuncDef dbDumpDriverFuncDef = {"dbDumpDriver",1,dbDumpDriverArgs, + "Dump device support information.\n" + "Example: dbDumpDriver pdbbase\n"}; static void dbDumpDriverCallFunc(const iocshArgBuf *args) { dbDumpDriver(*iocshPpdbbase); @@ -98,7 +115,10 @@ static void dbDumpLinkCallFunc(const iocshArgBuf *args) /* dbDumpRegistrar */ static const iocshArg * const dbDumpRegistrarArgs[] = { &argPdbbase}; -static const iocshFuncDef dbDumpRegistrarFuncDef = {"dbDumpRegistrar",1,dbDumpRegistrarArgs}; +static const iocshFuncDef dbDumpRegistrarFuncDef = {"dbDumpRegistrar",1,dbDumpRegistrarArgs, + "Dump list of registered functions including ones for subroutine records,\n" + "and ones that can be invoked from iocsh.\n" + "Example: dbDumpRegistrar pdbbase\n"}; static void dbDumpRegistrarCallFunc(const iocshArgBuf *args) { dbDumpRegistrar(*iocshPpdbbase); @@ -106,7 +126,9 @@ static void dbDumpRegistrarCallFunc(const iocshArgBuf *args) /* dbDumpFunction */ static const iocshArg * const dbDumpFunctionArgs[] = { &argPdbbase}; -static const iocshFuncDef dbDumpFunctionFuncDef = {"dbDumpFunction",1,dbDumpFunctionArgs}; +static const iocshFuncDef dbDumpFunctionFuncDef = {"dbDumpFunction",1,dbDumpFunctionArgs, + "Dump list of registered subroutine functions.\n" + "Example: dbDumpFunction pddbase\n"}; static void dbDumpFunctionCallFunc(const iocshArgBuf *args) { dbDumpFunction(*iocshPpdbbase); @@ -114,7 +136,9 @@ static void dbDumpFunctionCallFunc(const iocshArgBuf *args) /* dbDumpVariable */ static const iocshArg * const dbDumpVariableArgs[] = { &argPdbbase}; -static const iocshFuncDef dbDumpVariableFuncDef = {"dbDumpVariable",1,dbDumpVariableArgs}; +static const iocshFuncDef dbDumpVariableFuncDef = {"dbDumpVariable",1,dbDumpVariableArgs, + "Dump list of variables used in the database.\n" + "Example: dbDumpVariable pddbase\n"}; static void dbDumpVariableCallFunc(const iocshArgBuf *args) { dbDumpVariable(*iocshPpdbbase); @@ -124,8 +148,7 @@ static void dbDumpVariableCallFunc(const iocshArgBuf *args) static const iocshArg dbDumpBreaktableArg1 = { "tableName",iocshArgString}; static const iocshArg * const dbDumpBreaktableArgs[] = {&argPdbbase,&dbDumpBreaktableArg1}; -static const iocshFuncDef dbDumpBreaktableFuncDef = - {"dbDumpBreaktable",2,dbDumpBreaktableArgs}; +static const iocshFuncDef dbDumpBreaktableFuncDef = {"dbDumpBreaktable",2,dbDumpBreaktableArgs}; static void dbDumpBreaktableCallFunc(const iocshArgBuf *args) { dbDumpBreaktable(*iocshPpdbbase,args[1].sval); @@ -145,8 +168,7 @@ static void dbPvdDumpCallFunc(const iocshArgBuf *args) static const iocshArg dbPvdTableSizeArg0 = { "size",iocshArgInt}; static const iocshArg * const dbPvdTableSizeArgs[1] = {&dbPvdTableSizeArg0}; -static const iocshFuncDef dbPvdTableSizeFuncDef = - {"dbPvdTableSize",1,dbPvdTableSizeArgs}; +static const iocshFuncDef dbPvdTableSizeFuncDef = {"dbPvdTableSize",1,dbPvdTableSizeArgs}; static void dbPvdTableSizeCallFunc(const iocshArgBuf *args) { dbPvdTableSize(args[0].ival); @@ -154,8 +176,7 @@ static void dbPvdTableSizeCallFunc(const iocshArgBuf *args) /* dbReportDeviceConfig */ static const iocshArg * const dbReportDeviceConfigArgs[] = {&argPdbbase}; -static const iocshFuncDef dbReportDeviceConfigFuncDef = { - "dbReportDeviceConfig",1,dbReportDeviceConfigArgs}; +static const iocshFuncDef dbReportDeviceConfigFuncDef = {"dbReportDeviceConfig",1,dbReportDeviceConfigArgs}; static void dbReportDeviceConfigCallFunc(const iocshArgBuf *args) { dbReportDeviceConfig(*iocshPpdbbase,stdout); diff --git a/modules/database/src/ioc/dbtemplate/msi.cpp b/modules/database/src/ioc/dbtemplate/msi.cpp index 2e8c15747..6445c22f1 100644 --- a/modules/database/src/ioc/dbtemplate/msi.cpp +++ b/modules/database/src/ioc/dbtemplate/msi.cpp @@ -301,11 +301,11 @@ static void makeSubstitutions(inputData * const inputPvt, char *pstart; char *pend; int cmdind=-1; - int i; + size_t i; for (i = 0; i < NELEMENTS(cmdNames); i++) { if (strstr(command, cmdNames[i])) { - cmdind = i; + cmdind = (int)i; } } if (cmdind < 0) goto endcmd; diff --git a/modules/database/src/ioc/misc/miscIocRegister.c b/modules/database/src/ioc/misc/miscIocRegister.c index f88db7586..8c1180402 100644 --- a/modules/database/src/ioc/misc/miscIocRegister.c +++ b/modules/database/src/ioc/misc/miscIocRegister.c @@ -20,35 +20,43 @@ #include "miscIocRegister.h" /* iocInit */ -static const iocshFuncDef iocInitFuncDef = {"iocInit",0,NULL}; +static const iocshFuncDef iocInitFuncDef = {"iocInit",0,NULL, + "Initializes the various epics components and starts the IOC running.\n"}; static void iocInitCallFunc(const iocshArgBuf *args) { iocshSetError(iocInit()); } /* iocBuild */ -static const iocshFuncDef iocBuildFuncDef = {"iocBuild",0,NULL}; +static const iocshFuncDef iocBuildFuncDef = {"iocBuild",0,NULL, + "First step of the IOC initialization, puts the IOC into a ready-to-run (quiescent) state.\n" + "Needs iocRun() to make the IOC live.\n"}; static void iocBuildCallFunc(const iocshArgBuf *args) { iocshSetError(iocBuild()); } /* iocRun */ -static const iocshFuncDef iocRunFuncDef = {"iocRun",0,NULL}; +static const iocshFuncDef iocRunFuncDef = {"iocRun",0,NULL, + "Bring the IOC out of its initial quiescent state to the running state.\n" + "See more: iocBuild, iocPause"}; static void iocRunCallFunc(const iocshArgBuf *args) { iocshSetError(iocRun()); } /* iocPause */ -static const iocshFuncDef iocPauseFuncDef = {"iocPause",0,NULL}; +static const iocshFuncDef iocPauseFuncDef = {"iocPause",0,NULL, + "Brings a running IOC to a quiescent state with all record processing frozen.\n" + "See more: iocBuild, iocRub, iocInit"}; static void iocPauseCallFunc(const iocshArgBuf *args) { iocshSetError(iocPause()); } /* coreRelease */ -static const iocshFuncDef coreReleaseFuncDef = {"coreRelease",0,NULL}; +static const iocshFuncDef coreReleaseFuncDef = {"coreRelease",0,NULL, + "Print release information for iocCore.\n"}; static void coreReleaseCallFunc(const iocshArgBuf *args) { coreRelease (); @@ -75,7 +83,11 @@ void miscIocRegister(void) #ifndef SYSTEM_UNAVAILABLE static const iocshArg systemArg0 = { "command string",iocshArgString}; static const iocshArg * const systemArgs[] = {&systemArg0}; -static const iocshFuncDef systemFuncDef = {"system",1,systemArgs}; +static const iocshFuncDef systemFuncDef = {"system",1,systemArgs, + "Send command string to the system command interpreter for execution.\n" + "Not available on all OSs.\n" + "To enable this command, add registrar(iocshSystemCommand) to an application dbd file,\n" + "or include system.dbd\n"}; static void systemCallFunc(const iocshArgBuf *args) { iocshSetError(system(args[0].sval)); diff --git a/modules/database/src/ioc/misc/registerAllRecordDeviceDrivers.cpp b/modules/database/src/ioc/misc/registerAllRecordDeviceDrivers.cpp index 0afb997d4..8ba5182d0 100644 --- a/modules/database/src/ioc/misc/registerAllRecordDeviceDrivers.cpp +++ b/modules/database/src/ioc/misc/registerAllRecordDeviceDrivers.cpp @@ -185,6 +185,21 @@ registerAllRecordDeviceDrivers(DBBASE *pdbbase) registerJLinks(pdbbase, 1, ptr); } + // for each function() + for(ELLNODE *cur = ellFirst(&pdbbase->functionList); cur; cur = ellNext(cur)) { + dbText& reg = *CONTAINER(cur, dbText, node); + + typedef void(*registrar)(void); + registrar* ptr = lookupAs("pvar_func_register_func_", reg.text); + + if(!ptr || !*ptr) { + fprintf(stderr, "Unable to find function '%s' : %s\n", reg.text, epicsLoadError()); + return 1; + } + + runRegistrarOnce(*ptr); + } + // for each registrar() for(ELLNODE *cur = ellFirst(&pdbbase->registrarList); cur; cur = ellNext(cur)) { dbText& reg = *CONTAINER(cur, dbText, node); @@ -193,7 +208,7 @@ registerAllRecordDeviceDrivers(DBBASE *pdbbase) registrar* ptr = lookupAs("pvar_func_", reg.text); if(!ptr || !*ptr) { - fprintf(stderr, "Unable to find registar '%s' : %s\n", reg.text, epicsLoadError()); + fprintf(stderr, "Unable to find registrar '%s' : %s\n", reg.text, epicsLoadError()); return 1; } diff --git a/modules/database/src/ioc/rsrv/rsrvIocRegister.c b/modules/database/src/ioc/rsrv/rsrvIocRegister.c index 05b2e7d6b..533d67b52 100644 --- a/modules/database/src/ioc/rsrv/rsrvIocRegister.c +++ b/modules/database/src/ioc/rsrv/rsrvIocRegister.c @@ -20,7 +20,12 @@ /* casr */ static const iocshArg casrArg0 = { "level",iocshArgInt}; static const iocshArg * const casrArgs[1] = {&casrArg0}; -static const iocshFuncDef casrFuncDef = {"casr",1,casrArgs}; +static const iocshFuncDef casrFuncDef = {"casr",1,casrArgs, + "Channel Access Server Report with following levels:\n" + " 0 - server’s protocol version level and summary for each attached client\n" + " 1 - extends report with information about connected clients and beacons\n" + " 2 - extends report with specific channel names and UDP search requests\n" + " 3+ - expert\n"}; static void casrCallFunc(const iocshArgBuf *args) { casr(args[0].ival); diff --git a/modules/database/src/std/dev/devAaiSoft.c b/modules/database/src/std/dev/devAaiSoft.c index 55975bae0..f2e4a41f9 100644 --- a/modules/database/src/std/dev/devAaiSoft.c +++ b/modules/database/src/std/dev/devAaiSoft.c @@ -47,19 +47,15 @@ static long init_record(dbCommon *pcommon) aaiRecord *prec = (aaiRecord *)pcommon; DBLINK *plink = &prec->inp; - /* This is pass 0, link hasn't been initialized yet */ - dbInitLink(plink, DBF_INLINK); + /* Ask record to call us in pass 1 instead */ + if (prec->pact != AAI_DEVINIT_PASS1) { + return AAI_DEVINIT_PASS1; + } if (dbLinkIsConstant(plink)) { long nRequest = prec->nelm; long status; - /* Allocate a buffer, record support hasn't done that yet */ - if (!prec->bptr) { - prec->bptr = callocMustSucceed(nRequest, dbValueSize(prec->ftvl), - "devAaiSoft: buffer calloc failed"); - } - status = dbLoadLinkArray(plink, prec->ftvl, prec->bptr, &nRequest); if (!status) { prec->nord = nRequest; diff --git a/modules/database/src/std/filters/arr.c b/modules/database/src/std/filters/arr.c index 813848076..ffe3fce8f 100644 --- a/modules/database/src/std/filters/arr.c +++ b/modules/database/src/std/filters/arr.c @@ -13,16 +13,14 @@ #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "chfPlugin.h" +#include "dbAccessDefs.h" +#include "dbExtractArray.h" +#include "db_field_log.h" +#include "dbLock.h" +#include "epicsExit.h" +#include "freeList.h" +#include "epicsExport.h" typedef struct myStruct { epicsInt32 start; @@ -46,6 +44,8 @@ static void * allocPvt(void) myStruct *my = (myStruct*) freeListCalloc(myStructFreeList); if (!my) return NULL; + /* defaults */ + my->start = 0; my->incr = 1; my->end = -1; return (void *) my; @@ -77,8 +77,6 @@ static void freeArray(db_field_log *pfl) static long wrapArrayIndices(long *start, const long increment, long *end, const long no_elements) { - long len = 0; - if (*start < 0) *start = no_elements + *start; if (*start < 0) *start = 0; if (*start > no_elements) *start = no_elements; @@ -87,85 +85,53 @@ static long wrapArrayIndices(long *start, const long increment, long *end, if (*end < 0) *end = 0; if (*end >= no_elements) *end = no_elements - 1; - if (*end - *start >= 0) len = 1 + (*end - *start) / increment; - return len; + if (*end - *start >= 0) + return 1 + (*end - *start) / increment; + else + return 0; } static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) { myStruct *my = (myStruct*) pvt; - struct dbCommon *prec; - rset *prset; + int must_lock; long start = my->start; long end = my->end; - long nTarget = 0; + long nTarget; + void *pTarget; long offset = 0; - long nSource = dbChannelElements(chan); - long capacity = nSource; - void *pdst; + long nSource = pfl->no_elements; + void *pSource = pfl->u.r.field; switch (pfl->type) { case dbfl_type_val: - /* Only filter arrays */ + /* TODO Treat scalars as arrays with 1 element */ break; - case dbfl_type_rec: - /* Extract from record */ - if (dbChannelSpecial(chan) == SPC_DBADDR && - nSource > 1 && - (prset = dbGetRset(&chan->addr)) && - prset->get_array_info) - { - void *pfieldsave = dbChannelField(chan); - prec = dbChannelRecord(chan); - dbScanLock(prec); - prset->get_array_info(&chan->addr, &nSource, &offset); - nTarget = wrapArrayIndices(&start, my->incr, &end, nSource); - pfl->type = dbfl_type_ref; - pfl->stat = prec->stat; - pfl->sevr = prec->sevr; - pfl->time = prec->time; - pfl->field_type = dbChannelFieldType(chan); - pfl->field_size = dbChannelFieldSize(chan); - pfl->no_elements = nTarget; - if (nTarget) { - pdst = freeListCalloc(my->arrayFreeList); - if (pdst) { - pfl->u.r.dtor = freeArray; - pfl->u.r.pvt = my->arrayFreeList; - offset = (offset + start) % dbChannelElements(chan); - dbExtractArrayFromRec(&chan->addr, pdst, nTarget, capacity, - offset, my->incr); - pfl->u.r.field = pdst; - } - } - dbScanUnlock(prec); - dbChannelField(chan) = pfieldsave; - } - break; - - /* Extract from buffer */ case dbfl_type_ref: - pdst = NULL; - nSource = pfl->no_elements; - nTarget = wrapArrayIndices(&start, my->incr, &end, nSource); - pfl->no_elements = nTarget; - if (nTarget) { - /* Copy the data out */ - void *psrc = pfl->u.r.field; - - pdst = freeListCalloc(my->arrayFreeList); - if (!pdst) break; - offset = start; - dbExtractArrayFromBuf(psrc, pdst, pfl->field_size, pfl->field_type, - nTarget, nSource, offset, my->incr); + must_lock = !pfl->u.r.dtor; + if (must_lock) { + dbScanLock(dbChannelRecord(chan)); + dbChannelGetArrayInfo(chan, &pSource, &nSource, &offset); } - if (pfl->u.r.dtor) pfl->u.r.dtor(pfl); - if (nTarget) { + nTarget = wrapArrayIndices(&start, my->incr, &end, nSource); + if (nTarget > 0) { + /* copy the data */ + pTarget = freeListCalloc(my->arrayFreeList); + if (!pTarget) break; + /* must do the wrap-around with the original no_elements */ + offset = (offset + start) % pfl->no_elements; + dbExtractArray(pSource, pTarget, pfl->field_size, + nTarget, pfl->no_elements, offset, my->incr); + if (pfl->u.r.dtor) pfl->u.r.dtor(pfl); + pfl->u.r.field = pTarget; pfl->u.r.dtor = freeArray; pfl->u.r.pvt = my->arrayFreeList; - pfl->u.r.field = pdst; } + /* adjust no_elements (even if zero elements remain) */ + pfl->no_elements = nTarget; + if (must_lock) + dbScanUnlock(dbChannelRecord(chan)); break; } return pfl; diff --git a/modules/database/src/std/filters/ts.c b/modules/database/src/std/filters/ts.c index 8a07c3f2f..0feadb0f5 100644 --- a/modules/database/src/std/filters/ts.c +++ b/modules/database/src/std/filters/ts.c @@ -12,21 +12,44 @@ */ #include +#include +#include -#include -#include -#include -#include +#include "chfPlugin.h" +#include "db_field_log.h" +#include "dbExtractArray.h" +#include "dbLock.h" +#include "epicsExport.h" + +/* + * The size of the data is different for each channel, and can even + * change at runtime, so a freeList doesn't make much sense here. + */ +static void freeArray(db_field_log *pfl) { + free(pfl->u.r.field); +} static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) { epicsTimeStamp now; epicsTimeGetCurrent(&now); - /* If string or array, must make a copy (to ensure coherence between time and data) */ - if (pfl->type == dbfl_type_rec) { - dbScanLock(dbChannelRecord(chan)); - dbChannelMakeArrayCopy(pvt, pfl, chan); - dbScanUnlock(dbChannelRecord(chan)); + /* If reference and not already copied, + must make a copy (to ensure coherence between time and data) */ + if (pfl->type == dbfl_type_ref && !pfl->u.r.dtor) { + void *pTarget = calloc(pfl->no_elements, pfl->field_size); + void *pSource = pfl->u.r.field; + if (pTarget) { + long offset = 0; + long nSource = pfl->no_elements; + dbScanLock(dbChannelRecord(chan)); + dbChannelGetArrayInfo(chan, &pSource, &nSource, &offset); + dbExtractArray(pSource, pTarget, pfl->field_size, + nSource, pfl->no_elements, offset, 1); + pfl->u.r.field = pTarget; + pfl->u.r.dtor = freeArray; + pfl->u.r.pvt = pvt; + dbScanUnlock(dbChannelRecord(chan)); + } } pfl->time = now; diff --git a/modules/database/src/std/rec/aaiRecord.c b/modules/database/src/std/rec/aaiRecord.c index c1410067c..756b892ab 100644 --- a/modules/database/src/std/rec/aaiRecord.c +++ b/modules/database/src/std/rec/aaiRecord.c @@ -112,16 +112,18 @@ static long init_record(struct dbCommon *pcommon, int pass) prec->ftvl = DBF_UCHAR; prec->nord = (prec->nelm == 1); - /* we must call pdset->init_record in pass 0 - because it may set prec->bptr which must - not change after links are established before pass 1 - */ - + /* call pdset->init_record() in pass 0 so it can do its own + * memory allocation and set prec->bptr, which must be set by + * the end of pass 0. + */ if (pdset->common.init_record) { long status = pdset->common.init_record(pcommon); - /* init_record may set the bptr to point to the data */ - if (status) + if (status == AAI_DEVINIT_PASS1) { + /* requesting pass 1 callback, remember to do that */ + prec->pact = AAI_DEVINIT_PASS1; + } + else if (status) return status; } if (!prec->bptr) { @@ -132,6 +134,14 @@ static long init_record(struct dbCommon *pcommon, int pass) return 0; } + if (prec->pact == AAI_DEVINIT_PASS1) { + /* device support asked for an init_record() callback in pass 1 */ + long status = pdset->common.init_record(pcommon); + if (status) + return status; + prec->pact = FALSE; + } + recGblInitSimm(pcommon, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); /* must have read_aai function defined */ diff --git a/modules/database/src/std/rec/aaiRecord.dbd.pod b/modules/database/src/std/rec/aaiRecord.dbd.pod index 8eb8bad05..969f5800f 100644 --- a/modules/database/src/std/rec/aaiRecord.dbd.pod +++ b/modules/database/src/std/rec/aaiRecord.dbd.pod @@ -151,10 +151,15 @@ for more information on simulation mode and its fields. static long init_record(aaiRecord *prec, int pass) -If device support includes C, it is called. +If device support includes an C routine it is called, but unlike +most record types this occurs in pass 0, which allows the device support to +allocate the array buffer itself. + +Since EPICS 7.0.5 the device support may return C to request +a second call to its C routine in pass 1. Checks if device support allocated array space. If not, space for the array is -allocated using NELM and FTVL. The array address is stored in the record. +allocated using NELM and FTVL. The array address is stored in BPTR. This routine initializes SIMM with the value of SIML if SIML type is CONSTANT link or creates a channel access link if SIML type is PV_LINK. VAL is likewise @@ -294,10 +299,11 @@ Scan forward link if necessary, set PACT FALSE, and return. %/* Declare Device Support Entry Table */ %struct aaiRecord; %typedef struct aaidset { - % dset common; /*init_record returns: (-1,0)=>(failure,success)*/ + % dset common; /*init_record returns: (-1,0,AAI_DEVINIT_PASS1)=>(failure,success,callback)*/ % long (*read_aai)(struct aaiRecord *prec); /*returns: (-1,0)=>(failure,success)*/ %} aaidset; %#define HAS_aaidset + %#define AAI_DEVINIT_PASS1 2 % field(VAL,DBF_NOACCESS) { prompt("Value") @@ -469,8 +475,19 @@ with C set to 1. long init_record(dbCommon *precord) -This routine is optional. If provided, it is called by the record support -C routine. +This routine is optional. +If provided, it is called by the record support's C routine in +pass 0. +The device support may allocate memory for the VAL field's array (enough space +for NELM elements of type FTVA) from its own memory pool if desired, and store +the pointer to this buffer in the BPTR field. +The record will use C for this memory allocation if BPTR has not been +set by this routine. +The routine must return 0 for success, -1 or a error status on failure. + +Since EPICS 7.0.5 if this routine returns C in pass 0, it +will be called again in pass 1 with the PACT field set to C. +In pass 0 the PACT field is set to zero (FALSE). =head4 get_ioint_info @@ -485,7 +502,8 @@ provided for any device type that can use the ioEvent scanner. long read_aai(dbCommon *precord) -This routine must provide a new input value. It returns the following values: +This routine should provide a new input value. +It returns the following values: =over @@ -501,16 +519,15 @@ Other: Error. =head3 Device Support For Soft Records -The C<<< Soft Channel >>> device support module is provided to read values from -other records and store them in arrays. If INP is a constant link, then read_aai -does nothing. In this case, the record can be used to hold arrays written via -dbPuts. If INP is a database or channel access link, the new array value is read -from the link. NORD is set. +The C<<< Soft Channel >>> device support is provided to read values from other +records via the INP link, or to hold array values that are written into it. -This module places a value directly in VAL and NORD is set to the number of items -in the array. +If INP is a constant link the array value gets loaded from the link constant by +the C routine, which also sets NORD. +The C routine does nothing in this case. -If the INP link type is constant, then NORD is set to zero. +If INP is a database or channel access link, the C routine gets a +new array value from the link and sets NORD. =cut } diff --git a/modules/database/src/std/rec/compressRecord.c b/modules/database/src/std/rec/compressRecord.c index 98e17985f..d7da7f9b6 100644 --- a/modules/database/src/std/rec/compressRecord.c +++ b/modules/database/src/std/rec/compressRecord.c @@ -106,7 +106,7 @@ static void monitor(compressRecord *prec) db_post_events(prec, &prec->nuse, monitor_mask); prec->ouse = prec->nuse; } - db_post_events(prec, prec->bptr, monitor_mask); + db_post_events(prec, (void*)&prec->val, monitor_mask); } static void put_value(compressRecord *prec, double *psource, int n) @@ -404,7 +404,6 @@ static long cvt_dbaddr(DBADDR *paddr) { compressRecord *prec = (compressRecord *) paddr->precord; - paddr->pfield = prec->bptr; paddr->no_elements = prec->nsam; paddr->field_type = DBF_DOUBLE; paddr->field_size = sizeof(double); @@ -426,6 +425,8 @@ static long get_array_info(DBADDR *paddr, long *no_elements, long *offset) epicsUInt32 off = prec->off; epicsUInt32 nuse = prec->nuse; + paddr->pfield = prec->bptr; + if (prec->balg == bufferingALG_FIFO) { epicsUInt32 nsam = prec->nsam; diff --git a/modules/database/src/std/rec/histogramRecord.c b/modules/database/src/std/rec/histogramRecord.c index 0e9724cb6..44b278f39 100644 --- a/modules/database/src/std/rec/histogramRecord.c +++ b/modules/database/src/std/rec/histogramRecord.c @@ -111,7 +111,7 @@ static void wdogCallback(epicsCallback *arg) if (prec->mcnt > 0){ dbScanLock((struct dbCommon *)prec); recGblGetTimeStamp(prec); - db_post_events(prec, prec->bptr, DBE_VALUE | DBE_LOG); + db_post_events(prec, (void*)&prec->val, DBE_VALUE | DBE_LOG); prec->mcnt = 0; dbScanUnlock((struct dbCommon *)prec); } @@ -291,7 +291,7 @@ static void monitor(histogramRecord *prec) } /* send out monitors connected to the value field */ if (monitor_mask) - db_post_events(prec, prec->bptr, monitor_mask); + db_post_events(prec, (void*)&prec->val, monitor_mask); return; } @@ -300,7 +300,6 @@ static long cvt_dbaddr(DBADDR *paddr) { histogramRecord *prec = (histogramRecord *) paddr->precord; - paddr->pfield = prec->bptr; paddr->no_elements = prec->nelm; paddr->field_type = DBF_ULONG; paddr->field_size = sizeof(epicsUInt32); @@ -312,6 +311,7 @@ static long get_array_info(DBADDR *paddr, long *no_elements, long *offset) { histogramRecord *prec = (histogramRecord *) paddr->precord; + paddr->pfield = prec->bptr; *no_elements = prec->nelm; *offset = 0; return 0; diff --git a/modules/database/src/std/rec/subArrayRecord.c b/modules/database/src/std/rec/subArrayRecord.c index 82e543ca7..b73dee16b 100644 --- a/modules/database/src/std/rec/subArrayRecord.c +++ b/modules/database/src/std/rec/subArrayRecord.c @@ -161,7 +161,6 @@ static long cvt_dbaddr(DBADDR *paddr) { subArrayRecord *prec = (subArrayRecord *) paddr->precord; - paddr->pfield = prec->bptr; paddr->no_elements = prec->malm; paddr->field_type = prec->ftvl; paddr->field_size = dbValueSize(prec->ftvl); @@ -174,6 +173,7 @@ static long get_array_info(DBADDR *paddr, long *no_elements, long *offset) { subArrayRecord *prec = (subArrayRecord *) paddr->precord; + paddr->pfield = prec->bptr; if (prec->udf) *no_elements = 0; else @@ -293,7 +293,7 @@ static void monitor(subArrayRecord *prec) monitor_mask = recGblResetAlarms(prec); monitor_mask |= (DBE_LOG|DBE_VALUE); - db_post_events(prec, prec->bptr, monitor_mask); + db_post_events(prec, (void*)&prec->val, monitor_mask); return; } diff --git a/modules/database/src/template/top/exampleApp/src/xxxSupport.dbd b/modules/database/src/template/top/exampleApp/src/xxxSupport.dbd index 8094bdda6..64f2e3842 100644 --- a/modules/database/src/template/top/exampleApp/src/xxxSupport.dbd +++ b/modules/database/src/template/top/exampleApp/src/xxxSupport.dbd @@ -1,2 +1,2 @@ include "xxxRecord.dbd" -device(xxx,CONSTANT,devXxxSoft,"SoftChannel") +device(xxx,CONSTANT,devXxxSoft,"Soft Channel") diff --git a/modules/database/src/tools/DBD/Menu.pm b/modules/database/src/tools/DBD/Menu.pm index 38a2aba99..5480b3952 100644 --- a/modules/database/src/tools/DBD/Menu.pm +++ b/modules/database/src/tools/DBD/Menu.pm @@ -67,13 +67,16 @@ sub toDeclaration { my $name = $this->name; my $macro_name = "${name}_NUM_CHOICES"; my @choices = map { - sprintf " %-31s /* %s */", @{$_}[0], escapeCcomment(@{$_}[1]); + sprintf " %-31s /**< \@brief State string \"%s\" */", + @{$_}[0], escapeCcomment(@{$_}[1]); } $this->choices; my $num = scalar @choices; return "#ifndef $macro_name\n" . + "/** \@brief Enumerated type from menu $name */\n" . "typedef enum {\n" . join(",\n", @choices) . "\n} $name;\n" . + "/** \@brief Number of states defined for menu $name */\n" . "#define $macro_name $num\n" . "#endif\n\n"; } diff --git a/modules/database/src/tools/DBD/Parser.pm b/modules/database/src/tools/DBD/Parser.pm index d7e16ff52..b4cef4781 100644 --- a/modules/database/src/tools/DBD/Parser.pm +++ b/modules/database/src/tools/DBD/Parser.pm @@ -29,6 +29,7 @@ use DBD::Function; use DBD::Variable; our $debug=0; +our $allowAutoDeclarations=0; sub ParseDBD { (my $dbd, $_) = @_; @@ -102,8 +103,11 @@ sub ParseDBD { unquote($1, $2, $3, $4); my $rtyp = $dbd->recordtype($record_type); if (!defined $rtyp) { + my $msg = "Device '$choice' refers to unknown record type '$record_type'."; + dieContext($msg, "DBD files must be combined in the correct order.") + unless $allowAutoDeclarations; + warn "$msg\nRecord type '$record_type' declared.\n"; $rtyp = DBD::Recordtype->new($record_type); - warn "Device using unknown record type '$record_type', declaration created\n"; $dbd->add($rtyp); } $rtyp->add_device(DBD::Device->new($link_type, $dset, $choice)); diff --git a/modules/database/src/tools/DBD/Recfield.pm b/modules/database/src/tools/DBD/Recfield.pm index cc49bcc46..b4bd5d318 100644 --- a/modules/database/src/tools/DBD/Recfield.pm +++ b/modules/database/src/tools/DBD/Recfield.pm @@ -185,7 +185,7 @@ sub toDeclaration { my $name = $this->C_name; my $result = sprintf " %-19s %-12s", $ctype, "$name;"; my $prompt = $this->attribute('prompt'); - $result .= "/* $prompt */" if defined $prompt; + $result .= "/**< \@brief $prompt */" if defined $prompt; return $result; } @@ -217,7 +217,7 @@ sub toDeclaration { my $size = $this->attribute('size'); my $result = sprintf " %-19s %-12s", 'char', "${name}[${size}];"; my $prompt = $this->attribute('prompt'); - $result .= "/* $prompt */" if defined $prompt; + $result .= "/**< \@brief $prompt */" if defined $prompt; return $result; } @@ -540,7 +540,7 @@ sub toDeclaration { my $extra = $this->attribute('extra'); my $result = sprintf " %-31s ", "$extra;"; my $prompt = $this->attribute('prompt'); - $result .= "/* $prompt */" if defined $prompt; + $result .= "/**< \@brief $prompt */" if defined $prompt; return $result; } diff --git a/modules/database/src/tools/DBD/Recordtype.pm b/modules/database/src/tools/DBD/Recordtype.pm index a602f5da4..1e626fdf8 100644 --- a/modules/database/src/tools/DBD/Recordtype.pm +++ b/modules/database/src/tools/DBD/Recordtype.pm @@ -132,10 +132,15 @@ sub toDeclaration { $_->toDeclaration } $this->fields; my $name = $this->name; - $name .= "Record" unless $name eq "dbCommon"; - return "typedef struct $name {\n" . - join("\n", @fields) . - "\n} $name;\n\n"; + my $doc = $name; + if ($name ne 'dbCommon') { + $name .= 'Record'; + $doc .= ' record type.'; + } + return "/** \@brief Declaration of $doc */\n" . + "typedef struct $name {\n" . + join("\n", @fields) . + "\n} $name;\n\n"; } 1; diff --git a/modules/database/src/tools/dbdToMenuH.pl b/modules/database/src/tools/dbdToMenuH.pl index 10f1904fb..71352edff 100644 --- a/modules/database/src/tools/dbdToMenuH.pl +++ b/modules/database/src/tools/dbdToMenuH.pl @@ -11,6 +11,8 @@ use FindBin qw($Bin); use lib ("$Bin/../../lib/perl"); +use strict; + use EPICS::Getopts; use File::Basename; use DBD; @@ -57,7 +59,9 @@ if ($opt_D) { print map { "$_:\n" } @uniqfiles; } else { open OUTFILE, ">$outfile" or die "$tool: Can't open $outfile: $!\n"; - print OUTFILE "/* $outbase generated from $inbase */\n\n", + print OUTFILE "/** \@file $outbase\n", + " * \@brief Declarations generated from $inbase\n", + " */\n\n", "#ifndef $guard_name\n", "#define $guard_name\n\n"; my $menus = $dbd->menus; diff --git a/modules/database/src/tools/dbdToRecordtypeH.pl b/modules/database/src/tools/dbdToRecordtypeH.pl index dad767124..0bb3839fe 100644 --- a/modules/database/src/tools/dbdToRecordtypeH.pl +++ b/modules/database/src/tools/dbdToRecordtypeH.pl @@ -61,13 +61,19 @@ if ($opt_D) { # Output dependencies only, to stdout print "$outfile: ", join(" \\\n ", @uniqfiles), "\n\n"; print map { "$_:\n" } @uniqfiles; } else { + our ($rn, $rtyp) = each %{$rtypes}; + my $rtn = $rn; + $rtn .= 'Record' if $rn ne 'dbCommon'; + open OUTFILE, ">$outfile" or die "$tool: Can't open $outfile: $!\n"; - print OUTFILE "/* $outbase generated from $inbase */\n\n", + print OUTFILE "/** \@file $outbase\n", + " * \@brief Declarations for the \@ref $rtn \"$rn\" record type.\n", + " *\n", + " * This header was generated from $inbase\n", + " */\n\n", "#ifndef $guard_name\n", "#define $guard_name\n\n"; - our ($rn, $rtyp) = each %{$rtypes}; - print OUTFILE $rtyp->toCdefs; my @menu_fields = grep { @@ -111,31 +117,47 @@ if ($opt_D) { # Output dependencies only, to stdout sub oldtables { # Output compatible with R3.14.x + + my @fields = $rtyp->fields; + my $no_fields = scalar @fields; + + print OUTFILE << "__EOF__"; +#include +#include +#ifdef __cplusplus +extern "C" { +#endif +static int ${rn}RecordSizeOffset(dbRecordType *prt) +{ + ${rn}Record *prec = 0; + + if (prt->no_fields != ${no_fields}) { + cantProceed("IOC build or installation error:\\n" + " The ${rn}Record defined in the DBD file has %d fields,\\n" + " but the record support code was built with ${no_fields}.\\n", + prt->no_fields); + } +__EOF__ + print OUTFILE - "#include \n" . - "#include \n" . - "#ifdef __cplusplus\n" . - "extern \"C\" {\n" . - "#endif\n" . - "static int ${rn}RecordSizeOffset(dbRecordType *prt)\n" . - "{\n" . - " ${rn}Record *prec = 0;\n\n" . - " assert(prt->no_fields == " . scalar($rtyp->fields) . ");\n" . join("\n", map { - " prt->papFldDes[${rn}Record" . $_->name . "]->size = " . - "sizeof(prec->" . $_->C_name . ");" - } $rtyp->fields) . "\n" . - join("\n", map { - " prt->papFldDes[${rn}Record" . $_->name . "]->offset = (unsigned short)(" . - "(char *)&prec->" . $_->C_name . " - (char *)prec);" - } $rtyp->fields) . "\n" . - " prt->rec_size = sizeof(*prec);\n" . - " return 0;\n" . - "}\n" . - "epicsExportRegistrar(${rn}RecordSizeOffset);\n\n" . - "#ifdef __cplusplus\n" . - "}\n" . - "#endif\n"; + my $fn = $_->name; + my $cn = $_->C_name; + " prt->papFldDes[${rn}Record${fn}]->size = " . + "sizeof(prec->${cn});\n" . + " prt->papFldDes[${rn}Record${fn}]->offset = " . + "(unsigned short)((char *)&prec->${cn} - (char *)prec);" + } @fields), << "__EOF__"; + + prt->rec_size = sizeof(*prec); + return 0; +} +epicsExportRegistrar(${rn}RecordSizeOffset); + +#ifdef __cplusplus +} +#endif +__EOF__ } sub newtables { diff --git a/modules/database/src/tools/registerRecordDeviceDriver.pl b/modules/database/src/tools/registerRecordDeviceDriver.pl index 74b518a66..49354fbd7 100644 --- a/modules/database/src/tools/registerRecordDeviceDriver.pl +++ b/modules/database/src/tools/registerRecordDeviceDriver.pl @@ -31,6 +31,9 @@ my @path = map { split /[:;]/ } @opt_I; # FIXME: Broken on Win32? my ($file, $subname, $bldTop) = @ARGV; +# Auto-declaration of record types is needed to build loadable modules +$DBD::Parser::allowAutoDeclarations = 1; + my $dbd = DBD->new(); ParseDBD($dbd, Readfile($file, "", \@path)); diff --git a/modules/database/test/ioc/db/dbChArrTest.cpp b/modules/database/test/ioc/db/dbChArrTest.cpp index 90702b52e..9965852a8 100644 --- a/modules/database/test/ioc/db/dbChArrTest.cpp +++ b/modules/database/test/ioc/db/dbChArrTest.cpp @@ -131,7 +131,7 @@ static void check(short dbr_type) { memset(buf, 0, sizeof(buf)); \ (void) dbPutField(&offaddr, DBR_LONG, &off, 1); \ pfl = db_create_read_log(pch); \ - testOk(pfl && pfl->type == dbfl_type_rec, "Valid pfl, type = rec"); \ + testOk(pfl && pfl->type == dbfl_type_ref, "Valid pfl, type = ref"); \ testOk(!dbChannelGetField(pch, DBR_LONG, buf, NULL, &req, pfl), "Got Field value"); \ testOk(req == Size, "Got %ld elements (expected %d)", req, Size); \ if (!testOk(!memcmp(buf, Expected, sizeof(Expected)), "Data correct")) \ diff --git a/modules/database/test/std/filters/arrTest.cpp b/modules/database/test/std/filters/arrTest.cpp index bd83bd8ab..dfbbf463f 100644 --- a/modules/database/test/std/filters/arrTest.cpp +++ b/modules/database/test/std/filters/arrTest.cpp @@ -73,9 +73,9 @@ static int fl_equals_array(short type, const db_field_log *pfl1, void *p2) { } break; case DBR_STRING: - if (strtol(&((const char*)pfl1->u.r.field)[i*MAX_STRING_SIZE], NULL, 0) != ((epicsInt32*)p2)[i]) { + if (strtol(&((const char*)pfl1->u.r.field)[i*pfl1->field_size], NULL, 0) != ((epicsInt32*)p2)[i]) { testDiag("at index=%d: field log has '%s', should be '%d'", - i, &((const char*)pfl1->u.r.field)[i*MAX_STRING_SIZE], ((epicsInt32*)p2)[i]); + i, &((const char*)pfl1->u.r.field)[i*pfl1->field_size], ((epicsInt32*)p2)[i]); return 0; } break; @@ -120,7 +120,7 @@ static void testHead (const char *title, const char *typ = "") { off = Offset; \ (void) dbPutField(&offaddr, DBR_LONG, &off, 1); \ pfl = db_create_read_log(pch); \ - testOk(pfl->type == dbfl_type_rec, "original field log has type rec"); \ + testOk(pfl->type == dbfl_type_ref, "original field log has type ref"); \ pfl2 = dbChannelRunPostChain(pch, pfl); \ testOk(pfl2 == pfl, "call does not drop or replace field_log"); \ testOk(pfl->type == dbfl_type_ref, "filtered field log has type ref"); \ diff --git a/modules/database/test/std/filters/dbndTest.c b/modules/database/test/std/filters/dbndTest.c index e7897e28a..0206865fb 100644 --- a/modules/database/test/std/filters/dbndTest.c +++ b/modules/database/test/std/filters/dbndTest.c @@ -130,7 +130,7 @@ MAIN(dbndTest) dbEventCtx evtctx; int logsFree, logsFinal; - testPlan(77); + testPlan(72); testdbPrepare(); @@ -171,12 +171,9 @@ MAIN(dbndTest) "dbnd has one filter with argument in pre chain"); testOk((ellCount(&pch->post_chain) == 0), "dbnd has no filter in post chain"); - /* Field logs of type ref and rec: pass any update */ - - testHead("Field logs of type ref and rec"); - fl1.type = dbfl_type_rec; - mustPassTwice(pch, &fl1, "abs field_log=rec", 0., 0); + /* Field logs of type ref: pass any update */ + testHead("Field logs of type ref"); fl1.type = dbfl_type_ref; mustPassTwice(pch, &fl1, "abs field_log=ref", 0., 0); diff --git a/modules/database/test/tools/Menu.plt b/modules/database/test/tools/Menu.plt index 25b0c2020..8e667c833 100644 --- a/modules/database/test/tools/Menu.plt +++ b/modules/database/test/tools/Menu.plt @@ -30,10 +30,12 @@ is_deeply $menu->choice(2), undef, 'Third choice undefined'; like $menu->toDeclaration, qr/ ^ \s* \# \s* ifndef \s+ test_NUM_CHOICES \s* \n + \s* \/\*\* [^*]* \*\/ \s* \n \s* typedef \s+ enum \s+ \{ \s* \n - \s* ch1 \s+ \/\* [^*]* \*\/, \s* \n - \s* ch2 \s+ \/\* [^*]* \*\/ \s* \n + \s* ch1 \s+ \/\*\* [^*]* \*\/, \s* \n + \s* ch2 \s+ \/\*\* [^*]* \*\/ \s* \n \s* \} \s* test \s* ; \s* \n + \s* \/\*\* [^*]* \*\/ \s* \n \s* \# \s* define \s+ test_NUM_CHOICES \s+ 2 \s* \n \s* \# \s* endif \s* \n \s* $ /x, 'C declaration'; diff --git a/modules/libcom/src/error/errlog.h b/modules/libcom/src/error/errlog.h index 425e78ac6..02f2c0816 100644 --- a/modules/libcom/src/error/errlog.h +++ b/modules/libcom/src/error/errlog.h @@ -11,6 +11,20 @@ #ifndef INC_errlog_H #define INC_errlog_H +/** \file errlog.h + * \brief Functions for interacting with the errlog task + * + * This file contains functions for passing error messages with varying severity, + * registering and un-registering listeners and modifying the log buffer size and + * max message size. + * + * Some of these functions are similar to the standard C library functions printf + * and vprintf. For details on the arguments and return codes it is useful to consult + * any book that describes the standard C library such as + * `The C Programming Language ANSI C Edition` by Kernighan and Ritchie. + * + */ + #include #include #include @@ -22,8 +36,15 @@ extern "C" { #endif +/** + * errlogListener function type. + * + * This is used when adding or removing log listeners in ::errlogAddListener + * and ::errlogRemoveListeners. + */ typedef void (*errlogListener)(void *pPrivate, const char *message); +/** errlog severity enums */ typedef enum { errlogInfo, errlogMinor, @@ -45,37 +66,171 @@ LIBCOM_API extern int errVerbose; LIBCOM_API extern const char * errlogSevEnumString[]; #endif -/* errMessage is a macro so it can get the file and line number */ +/** + * errMessage is a macro so it can get the file and line number. It prints the message, + * the status symbol and string values, and the name of the task which invoked errMessage. + * It also prints the name of the source file and the line number from which the call was issued. + * + * The status code used for the 1st argument is: + * - 0: Find latest vxWorks or Unix error (errno value). + * - -1: Don’t report status. + * - Other: Use this status code and lookup the string value + * + * \param S Status code + * \param PM The message to print + */ #define errMessage(S, PM) \ errPrintf(S, __FILE__, __LINE__, "%s", PM) -/* epicsPrintf and epicsVprintf are old names for errlog routines*/ + +/** epicsPrintf is an old name for errlog routines */ #define epicsPrintf errlogPrintf + +/** epicsVprintf is an old name for errlog routines */ #define epicsVprintf errlogVprintf +/** + * errlogPrintf is like the printf function provided by the standard C library, except + * that the output is sent to the errlog task. Unless configured not to, the output + * will appear on the console as well. + */ LIBCOM_API int errlogPrintf(const char *pformat, ...) EPICS_PRINTF_STYLE(1,2); + +/** + * errlogVprintf is like the vprintf function provided by the standard C library, except + * that the output is sent to the errlog task. Unless configured not to, the output + * will appear on the console as well. + */ LIBCOM_API int errlogVprintf(const char *pformat, va_list pvar); + +/** + * This function is like ::errlogPrintf except that it adds the severity to the beginning + * of the message in the form `sevr=` where value is one of the enumerated + * severities in ::errlogSevEnum. Also the message is suppressed if severity is less than + * the current severity to suppress. + * + * \param severity One of the severity enums from ::errlogSevEnum + * \param pFormat The message to log or print + * \return int Consult printf documentation in C standard library + */ LIBCOM_API int errlogSevPrintf(const errlogSevEnum severity, const char *pformat, ...) EPICS_PRINTF_STYLE(2,3); + +/** + * This function is like ::errlogVprintf except that it adds the severity to the beginning + * of the message in the form `sevr=` where value is one of the enumerated + * severities in ::errlogSevEnum. Also the message is suppressed if severity is less than + * the current severity to suppress. If epicsThreadIsOkToBlock is true, which is + * true during iocInit, errlogSevVprintf does NOT send output to the + * errlog task. + * + * \param severity One of the severity enums from ::errlogSevEnum + * \param pFormat The message to log or print + * \param pvar va_list + * \return int Consult printf documentation in C standard library + */ LIBCOM_API int errlogSevVprintf(const errlogSevEnum severity, const char *pformat, va_list pvar); + +/** + * Sends message to the errlog task. + * + * \param message The message to send + */ LIBCOM_API int errlogMessage(const char *message); +/** + * Gets the string value of severity. + * + * \param severity The severity from ::errlogSevEnum + * \return The string value + */ LIBCOM_API const char * errlogGetSevEnumString(errlogSevEnum severity); + +/** + * Sets the severity to log + * + * \param severity The severity from ::errlogSevEnum + */ LIBCOM_API void errlogSetSevToLog(errlogSevEnum severity); + +/** + * Gets the current severity to log + * + * \return ::errlogSevEnum + */ LIBCOM_API errlogSevEnum errlogGetSevToLog(void); +/** + * Any code can receive errlog message. This function will add a listener callback. + * + * \param listener Function pointer of type ::errlogListener + * \param pPrivate This will be passed as the first argument of listener() + */ LIBCOM_API void errlogAddListener(errlogListener listener, void *pPrivate); + +/** + * This function will remove a listener callback. + * + * \param listener Function pointer of type ::errlogListener + * \param pPrivate This will be passed as the first argument of listener() + */ LIBCOM_API int errlogRemoveListeners(errlogListener listener, void *pPrivate); +/** + * Normally the errlog system displays all messages on the console. + * During error message storms this function can be used to suppress console messages. + * A argument of 0 suppresses the messages, any other value lets messages go to the console. + * + * \param yesno (0=No, 1=Yes) + * \return 0 + */ LIBCOM_API int eltc(int yesno); + +/** + * Sets a new stream to write the messages to + * + * \param stream Pointer to file handle + * \return 0 + */ LIBCOM_API int errlogSetConsole(FILE *stream); +/** + * Can be used to initialize the error logging system with a larger buffer. The default buffer size is 1280 bytes. + * + * \param bufsize The desired buffer size + */ LIBCOM_API int errlogInit(int bufsize); + +/** + * errlogInit2 can be used to initialize the error logging system with a larger buffer and maximum message size. + * The default buffer size is 1280 bytes, and the default maximum message size is 256. + * + * \param bufsize The desired buffer size + * \param maxMsgSize The desired max message size + */ LIBCOM_API int errlogInit2(int bufsize, int maxMsgSize); + +/** Wakes up the errlog task and then waits until all messages are flushed from the queue. */ LIBCOM_API void errlogFlush(void); +/** + * Routine errPrintf is normally called as follows: + * `errPrintf(status, __FILE__, __LINE__,"",...); ` + * + * Where status is defined as: + * - 0: Find latest vxWorks or Unix error. + * - -1: Don’t report status. + * - Other: Use this status code and lookup the string value + * + * \param status See above + * \param __FILE__ As shown or NULL if the file name and line number should not be printed. + * \param __LINE__ As shown + * + * The remaining arguments are just like the arguments to the C printf routine. + * ::errVerbose determines if the filename and line number are shown. + */ LIBCOM_API void errPrintf(long status, const char *pFileName, int lineno, const char *pformat, ...) EPICS_PRINTF_STYLE(4,5); @@ -83,6 +238,13 @@ LIBCOM_API int errlogPrintfNoConsole(const char *pformat, ...) EPICS_PRINTF_STYLE(1,2); LIBCOM_API int errlogVprintfNoConsole(const char *pformat,va_list pvar); +/** + * Lookup the status code and return the string value in pBuf + * + * \param status The status code to lookup + * \param pBuf The char buffer to write the string value into + * \param bufLength The max size of pBuf + */ LIBCOM_API void errSymLookup(long status, char *pBuf, size_t bufLength); #ifdef __cplusplus diff --git a/modules/libcom/src/iocsh/iocsh.cpp b/modules/libcom/src/iocsh/iocsh.cpp index b626e1530..474436c72 100644 --- a/modules/libcom/src/iocsh/iocsh.cpp +++ b/modules/libcom/src/iocsh/iocsh.cpp @@ -163,7 +163,12 @@ const iocshCmdDef * epicsStdCall iocshFindCommand(const char *name) static const iocshArg varCmdArg0 = { "[variable", iocshArgString}; static const iocshArg varCmdArg1 = { "[value]]", iocshArgString}; static const iocshArg *varCmdArgs[2] = {&varCmdArg0, &varCmdArg1}; -static const iocshFuncDef varFuncDef = {"var", 2, varCmdArgs}; +static const iocshFuncDef varFuncDef = {"var", 2, varCmdArgs, + "Print all, print single variable or set value to single variable\n" + " (default) - print all variables and their values" + " defined in database definitions files\n" + " variable - if only parameter print value for this variable\n" + " value - set the value to variable\n"}; void epicsStdCall iocshRegisterVariable (const iocshVarDef *piocshVarDef) { @@ -1139,7 +1144,10 @@ static void varCallFunc(const iocshArgBuf *args) /* iocshCmd */ static const iocshArg iocshCmdArg0 = { "command",iocshArgString}; static const iocshArg *iocshCmdArgs[1] = {&iocshCmdArg0}; -static const iocshFuncDef iocshCmdFuncDef = {"iocshCmd",1,iocshCmdArgs}; +static const iocshFuncDef iocshCmdFuncDef = {"iocshCmd",1,iocshCmdArgs, + "Takes a single IOC shell command and executes it\n" + " * This function is most useful to execute a single IOC shell command\n" + " from vxWorks or RTEMS startup script (or command line)\n"}; static void iocshCmdCallFunc(const iocshArgBuf *args) { iocshCmd(args[0].sval); @@ -1149,7 +1157,9 @@ static void iocshCmdCallFunc(const iocshArgBuf *args) static const iocshArg iocshLoadArg0 = { "pathname",iocshArgString}; static const iocshArg iocshLoadArg1 = { "macros", iocshArgString}; static const iocshArg *iocshLoadArgs[2] = {&iocshLoadArg0, &iocshLoadArg1}; -static const iocshFuncDef iocshLoadFuncDef = {"iocshLoad",2,iocshLoadArgs}; +static const iocshFuncDef iocshLoadFuncDef = {"iocshLoad",2,iocshLoadArgs, + "Execute IOC shell commands provided in file from first parameter\n" + " * (optional) replace macros within the file with provided values\n"}; static void iocshLoadCallFunc(const iocshArgBuf *args) { iocshLoad(args[0].sval, args[1].sval); @@ -1159,7 +1169,10 @@ static void iocshLoadCallFunc(const iocshArgBuf *args) static const iocshArg iocshRunArg0 = { "command",iocshArgString}; static const iocshArg iocshRunArg1 = { "macros", iocshArgString}; static const iocshArg *iocshRunArgs[2] = {&iocshRunArg0, &iocshRunArg1}; -static const iocshFuncDef iocshRunFuncDef = {"iocshRun",2,iocshRunArgs}; +static const iocshFuncDef iocshRunFuncDef = {"iocshRun",2,iocshRunArgs, + "Takes a single IOC shell command, replaces macros and executes it\n" + " * This function is most useful to execute a single IOC shell command\n" + " from vxWorks or RTEMS startup script (or command line)\n"}; static void iocshRunCallFunc(const iocshArgBuf *args) { iocshRun(args[0].sval, args[1].sval); diff --git a/modules/libcom/src/iocsh/libComRegister.c b/modules/libcom/src/iocsh/libComRegister.c index 12ec7a9fe..1846bcee3 100644 --- a/modules/libcom/src/iocsh/libComRegister.c +++ b/modules/libcom/src/iocsh/libComRegister.c @@ -46,7 +46,9 @@ void date(const char *format) static const iocshArg dateArg0 = { "format",iocshArgString}; static const iocshArg * const dateArgs[] = {&dateArg0}; -static const iocshFuncDef dateFuncDef = {"date", 1, dateArgs}; +static const iocshFuncDef dateFuncDef = {"date", 1, dateArgs, + "Print current date and time\n" + " (default) - '%Y/%m/%d %H:%M:%S.%06f'\n"}; static void dateCallFunc (const iocshArgBuf *args) { date(args[0].sval); @@ -64,7 +66,8 @@ IOCSH_STATIC_FUNC void echo(char* str) static const iocshArg echoArg0 = { "string",iocshArgString}; static const iocshArg * const echoArgs[1] = {&echoArg0}; -static const iocshFuncDef echoFuncDef = {"echo",1,echoArgs}; +static const iocshFuncDef echoFuncDef = {"echo",1,echoArgs, + "Print string after expanding macros and environment variables\n"}; static void echoCallFunc(const iocshArgBuf *args) { echo(args[0].sval); @@ -73,7 +76,8 @@ static void echoCallFunc(const iocshArgBuf *args) /* chdir */ static const iocshArg chdirArg0 = { "directory name",iocshArgString}; static const iocshArg * const chdirArgs[1] = {&chdirArg0}; -static const iocshFuncDef chdirFuncDef = {"cd",1,chdirArgs}; +static const iocshFuncDef chdirFuncDef = {"cd",1,chdirArgs, + "Change directory to new directory provided as parameter\n"}; static void chdirCallFunc(const iocshArgBuf *args) { if (args[0].sval == NULL || @@ -83,7 +87,8 @@ static void chdirCallFunc(const iocshArgBuf *args) } /* print current working directory */ -static const iocshFuncDef pwdFuncDef = { "pwd", 0, 0 }; +static const iocshFuncDef pwdFuncDef = {"pwd", 0, 0, + "Print name of current/working directory\n"}; static void pwdCallFunc (const iocshArgBuf *args) { char buf[256]; @@ -97,7 +102,8 @@ static void pwdCallFunc (const iocshArgBuf *args) static const iocshArg epicsEnvSetArg0 = { "name",iocshArgString}; static const iocshArg epicsEnvSetArg1 = { "value",iocshArgString}; static const iocshArg * const epicsEnvSetArgs[2] = {&epicsEnvSetArg0,&epicsEnvSetArg1}; -static const iocshFuncDef epicsEnvSetFuncDef = {"epicsEnvSet",2,epicsEnvSetArgs}; +static const iocshFuncDef epicsEnvSetFuncDef = {"epicsEnvSet",2,epicsEnvSetArgs, + "Set environment variable name to value\n"}; static void epicsEnvSetCallFunc(const iocshArgBuf *args) { char *name = args[0].sval; @@ -117,7 +123,8 @@ static void epicsEnvSetCallFunc(const iocshArgBuf *args) /* epicsEnvUnset */ static const iocshArg epicsEnvUnsetArg0 = { "name",iocshArgString}; static const iocshArg * const epicsEnvUnsetArgs[1] = {&epicsEnvUnsetArg0}; -static const iocshFuncDef epicsEnvUnsetFuncDef = {"epicsEnvUnset",1,epicsEnvUnsetArgs}; +static const iocshFuncDef epicsEnvUnsetFuncDef = {"epicsEnvUnset",1,epicsEnvUnsetArgs, + "Remove variable name from the environment\n"}; static void epicsEnvUnsetCallFunc(const iocshArgBuf *args) { char *name = args[0].sval; @@ -135,14 +142,16 @@ IOCSH_STATIC_FUNC void epicsParamShow() epicsPrtEnvParams (); } -static const iocshFuncDef epicsParamShowFuncDef = {"epicsParamShow",0,NULL}; +static const iocshFuncDef epicsParamShowFuncDef = {"epicsParamShow",0,NULL, + "Show the environment variable parameters used by iocCore\n"}; static void epicsParamShowCallFunc(const iocshArgBuf *args) { epicsParamShow (); } /* epicsPrtEnvParams */ -static const iocshFuncDef epicsPrtEnvParamsFuncDef = {"epicsPrtEnvParams",0,0}; +static const iocshFuncDef epicsPrtEnvParamsFuncDef = {"epicsPrtEnvParams",0,0, + "Show the environment variable parameters used by iocCore\n"}; static void epicsPrtEnvParamsCallFunc(const iocshArgBuf *args) { epicsPrtEnvParams (); @@ -151,21 +160,29 @@ static void epicsPrtEnvParamsCallFunc(const iocshArgBuf *args) /* epicsEnvShow */ static const iocshArg epicsEnvShowArg0 = { "[name]",iocshArgString}; static const iocshArg * const epicsEnvShowArgs[1] = {&epicsEnvShowArg0}; -static const iocshFuncDef epicsEnvShowFuncDef = {"epicsEnvShow",1,epicsEnvShowArgs}; +static const iocshFuncDef epicsEnvShowFuncDef = {"epicsEnvShow",1,epicsEnvShowArgs, + "Show environment variables on your system\n" + " (default) - show all environment variables\n" + " name - show value of specific environment variable\n"}; static void epicsEnvShowCallFunc(const iocshArgBuf *args) { epicsEnvShow (args[0].sval); } /* registryDump */ -static const iocshFuncDef registryDumpFuncDef = {"registryDump",0,NULL}; +static const iocshFuncDef registryDumpFuncDef = {"registryDump",0,NULL, + "Dump a hash table of EPICS registry\n"}; static void registryDumpCallFunc(const iocshArgBuf *args) { registryDump (); } /* iocLogInit */ -static const iocshFuncDef iocLogInitFuncDef = {"iocLogInit",0}; +static const iocshFuncDef iocLogInitFuncDef = {"iocLogInit",0,0, + "Initialize IOC logging\n" + " * EPICS environment variable 'EPICS_IOC_LOG_INET' has to be defined\n" + " * Logging controled via 'iocLogDisable' variable\n" + " see 'setIocLogDisable' command\n"}; static void iocLogInitCallFunc(const iocshArgBuf *args) { iocLogInit (); @@ -179,7 +196,10 @@ IOCSH_STATIC_FUNC void setIocLogDisable(int val) static const iocshArg iocLogDisableArg0 = {"(0,1)=>(false,true)",iocshArgInt}; static const iocshArg * const iocLogDisableArgs[1] = {&iocLogDisableArg0}; -static const iocshFuncDef iocLogDisableFuncDef = {"setIocLogDisable",1,iocLogDisableArgs}; +static const iocshFuncDef iocLogDisableFuncDef = {"setIocLogDisable",1,iocLogDisableArgs, + "Controls the 'iocLogDisable' variable\n" + " 0 - enable logging\n" + " 1 - disable logging\n"}; static void iocLogDisableCallFunc(const iocshArgBuf *args) { setIocLogDisable(args[0].ival); @@ -188,7 +208,8 @@ static void iocLogDisableCallFunc(const iocshArgBuf *args) /* iocLogShow */ static const iocshArg iocLogShowArg0 = {"level",iocshArgInt}; static const iocshArg * const iocLogShowArgs[1] = {&iocLogShowArg0}; -static const iocshFuncDef iocLogShowFuncDef = {"iocLogShow",1,iocLogShowArgs}; +static const iocshFuncDef iocLogShowFuncDef = {"iocLogShow",1,iocLogShowArgs, + "Determine if a IOC Log Prefix has been set\n"}; static void iocLogShowCallFunc(const iocshArgBuf *args) { iocLogShow (args[0].ival); @@ -197,17 +218,22 @@ static void iocLogShowCallFunc(const iocshArgBuf *args) /* eltc */ static const iocshArg eltcArg0 = {"(0,1)=>(false,true)",iocshArgInt}; static const iocshArg * const eltcArgs[1] = {&eltcArg0}; -static const iocshFuncDef eltcFuncDef = {"eltc",1,eltcArgs}; +static const iocshFuncDef eltcFuncDef = {"eltc",1,eltcArgs, + "Control display of error log messages on console\n" + " 0 - no\n" + " 1 - yes (default)\n"}; static void eltcCallFunc(const iocshArgBuf *args) { eltc(args[0].ival); } /* errlogInit */ -static const iocshArg errlogInitArg0 = { "bufsize",iocshArgInt}; +static const iocshArg errlogInitArg0 = { "bufSize",iocshArgInt}; static const iocshArg * const errlogInitArgs[1] = {&errlogInitArg0}; static const iocshFuncDef errlogInitFuncDef = - {"errlogInit",1,errlogInitArgs}; + {"errlogInit",1,errlogInitArgs, + "Initialize error log client buffer size\n" + " bufSize - size of circular buffer (default = 1280 bytes)\n"}; static void errlogInitCallFunc(const iocshArgBuf *args) { errlogInit(args[0].ival); @@ -219,7 +245,10 @@ static const iocshArg errlogInit2Arg1 = { "maxMsgSize",iocshArgInt}; static const iocshArg * const errlogInit2Args[] = {&errlogInit2Arg0, &errlogInit2Arg1}; static const iocshFuncDef errlogInit2FuncDef = - {"errlogInit2", 2, errlogInit2Args}; + {"errlogInit2", 2, errlogInit2Args, + "Initialize error log client buffer size and maximum message size\n" + " bufSize - size of circular buffer (default = 1280 bytes)\n" + " maxMsgSize - maximum size of error message (default = 256 bytes)\n"}; static void errlogInit2CallFunc(const iocshArgBuf *args) { errlogInit2(args[0].ival, args[1].ival); @@ -233,7 +262,8 @@ IOCSH_STATIC_FUNC void errlog(const char *message) static const iocshArg errlogArg0 = { "message",iocshArgString}; static const iocshArg * const errlogArgs[1] = {&errlogArg0}; -static const iocshFuncDef errlogFuncDef = {"errlog",1,errlogArgs}; +static const iocshFuncDef errlogFuncDef = {"errlog",1,errlogArgs, + "Send message to errlog\n"}; static void errlogCallFunc(const iocshArgBuf *args) { errlog(args[0].sval); @@ -243,7 +273,8 @@ static void errlogCallFunc(const iocshArgBuf *args) /* iocLogPrefix */ static const iocshArg iocLogPrefixArg0 = { "prefix",iocshArgString}; static const iocshArg * const iocLogPrefixArgs[1] = {&iocLogPrefixArg0}; -static const iocshFuncDef iocLogPrefixFuncDef = {"iocLogPrefix",1,iocLogPrefixArgs}; +static const iocshFuncDef iocLogPrefixFuncDef = {"iocLogPrefix",1,iocLogPrefixArgs, + "Create the prefix for all messages going into IOC log\n"}; static void iocLogPrefixCallFunc(const iocshArgBuf *args) { iocLogPrefix(args[0].sval); @@ -253,7 +284,8 @@ static void iocLogPrefixCallFunc(const iocshArgBuf *args) static const iocshArg epicsThreadShowAllArg0 = { "level",iocshArgInt}; static const iocshArg * const epicsThreadShowAllArgs[1] = {&epicsThreadShowAllArg0}; static const iocshFuncDef epicsThreadShowAllFuncDef = - {"epicsThreadShowAll",1,epicsThreadShowAllArgs}; + {"epicsThreadShowAll",1,epicsThreadShowAllArgs, + "Display info about all threads\n"}; static void epicsThreadShowAllCallFunc(const iocshArgBuf *args) { epicsThreadShowAll(args[0].ival); @@ -262,7 +294,8 @@ static void epicsThreadShowAllCallFunc(const iocshArgBuf *args) /* epicsThreadShow */ static const iocshArg threadArg0 = { "[-level] [thread ...]", iocshArgArgv}; static const iocshArg * const threadArgs[1] = { &threadArg0 }; -static const iocshFuncDef threadFuncDef = {"epicsThreadShow",1,threadArgs}; +static const iocshFuncDef threadFuncDef = {"epicsThreadShow",1,threadArgs, + "Display info about the specified thread\n"}; static void threadCallFunc(const iocshArgBuf *args) { int i = 1; @@ -308,7 +341,8 @@ static void threadCallFunc(const iocshArgBuf *args) static const iocshArg taskwdShowArg0 = { "level",iocshArgInt}; static const iocshArg * const taskwdShowArgs[1] = {&taskwdShowArg0}; static const iocshFuncDef taskwdShowFuncDef = - {"taskwdShow",1,taskwdShowArgs}; + {"taskwdShow",1,taskwdShowArgs, + "Show number of tasks and monitors registered\n"}; static void taskwdShowCallFunc(const iocshArgBuf *args) { taskwdShow(args[0].ival); @@ -320,7 +354,10 @@ static const iocshArg epicsMutexShowAllArg1 = { "level",iocshArgInt}; static const iocshArg * const epicsMutexShowAllArgs[2] = {&epicsMutexShowAllArg0,&epicsMutexShowAllArg1}; static const iocshFuncDef epicsMutexShowAllFuncDef = - {"epicsMutexShowAll",2,epicsMutexShowAllArgs}; + {"epicsMutexShowAll",2,epicsMutexShowAllArgs, + "Display information about all epicsMutex semaphores\n" + " onlyLocked - non-zero to show only locked semaphores\n" + " level - desired information level to report\n"}; static void epicsMutexShowAllCallFunc(const iocshArgBuf *args) { epicsMutexShowAll(args[0].ival,args[1].ival); @@ -330,7 +367,8 @@ static void epicsMutexShowAllCallFunc(const iocshArgBuf *args) static const iocshArg epicsThreadSleepArg0 = { "seconds",iocshArgDouble}; static const iocshArg * const epicsThreadSleepArgs[1] = {&epicsThreadSleepArg0}; static const iocshFuncDef epicsThreadSleepFuncDef = - {"epicsThreadSleep",1,epicsThreadSleepArgs}; + {"epicsThreadSleep",1,epicsThreadSleepArgs, + "Pause execution of IOC shell for seconds\n"}; static void epicsThreadSleepCallFunc(const iocshArgBuf *args) { epicsThreadSleep(args[0].dval); @@ -339,7 +377,10 @@ static void epicsThreadSleepCallFunc(const iocshArgBuf *args) /* epicsThreadResume */ static const iocshArg epicsThreadResumeArg0 = { "[thread ...]", iocshArgArgv}; static const iocshArg * const epicsThreadResumeArgs[1] = { &epicsThreadResumeArg0 }; -static const iocshFuncDef epicsThreadResumeFuncDef = {"epicsThreadResume",1,epicsThreadResumeArgs}; +static const iocshFuncDef epicsThreadResumeFuncDef = {"epicsThreadResume",1,epicsThreadResumeArgs, + "Resume a suspended thread.\n" + "Only do this if you know that it is safe to " + "resume a suspended thread\n"}; static void epicsThreadResumeCallFunc(const iocshArgBuf *args) { int i; @@ -381,14 +422,19 @@ static void epicsThreadResumeCallFunc(const iocshArgBuf *args) /* generalTimeReport */ static const iocshArg generalTimeReportArg0 = { "interest_level", iocshArgArgv}; static const iocshArg * const generalTimeReportArgs[1] = { &generalTimeReportArg0 }; -static const iocshFuncDef generalTimeReportFuncDef = {"generalTimeReport",1,generalTimeReportArgs}; +static const iocshFuncDef generalTimeReportFuncDef = {"generalTimeReport",1,generalTimeReportArgs, + "Display time providers and their priority levels" + " for current and event times\n"}; static void generalTimeReportCallFunc(const iocshArgBuf *args) { generalTimeReport(args[0].ival); } /* installLastResortEventProvider */ -static const iocshFuncDef installLastResortEventProviderFuncDef = {"installLastResortEventProvider", 0, NULL}; +static const iocshFuncDef installLastResortEventProviderFuncDef = {"installLastResortEventProvider", 0, NULL, + "Installs the optional Last Resort event provider" + " at priority 999,\nwhich returns the current time" + " for every event number\n"}; static void installLastResortEventProviderCallFunc(const iocshArgBuf *args) { installLastResortEventProvider(); diff --git a/modules/libcom/src/misc/epicsStdlib.h b/modules/libcom/src/misc/epicsStdlib.h index 219a1fb0a..e3cb69f91 100644 --- a/modules/libcom/src/misc/epicsStdlib.h +++ b/modules/libcom/src/misc/epicsStdlib.h @@ -13,6 +13,20 @@ #ifndef INC_epicsStdlib_H #define INC_epicsStdlib_H +/** + * \file epicsStdlib.h + * \brief Functions to convert strings to primative types + * + * These routines convert a string into an integer of the indicated type and + * number base, or into a floating point type. The units pointer argument may + * be NULL, but if not it will be left pointing to the first non-whitespace + * character following the numeric string, or to the terminating zero byte. + * + * The return value from these routines is a status code, zero meaning OK. + * For the macro functions beginning with `epicsScan` the return code is 0 + * or 1 (0=failure or 1=success, similar to the sscanf() function). + */ + #include #include @@ -25,57 +39,167 @@ extern "C" { #endif +/** Return code for `No digits to convert` */ #define S_stdlib_noConversion (M_stdlib | 1) /* No digits to convert */ +/** Return code for `Extraneous characters` */ #define S_stdlib_extraneous (M_stdlib | 2) /* Extraneous characters */ +/** Return code for `Too small to represent` */ #define S_stdlib_underflow (M_stdlib | 3) /* Too small to represent */ +/** Return code for `Too large to represent` */ #define S_stdlib_overflow (M_stdlib | 4) /* Too large to represent */ +/** Return code for `Number base not supported` */ #define S_stdlib_badBase (M_stdlib | 5) /* Number base not supported */ - -LIBCOM_API int +/** + * \brief Convert a string to a long type + * + * \param str Pointer to a constant character array + * \param to Pointer to the specified type (this will be set during the conversion) + * \param base The number base to use + * \param units Pointer to a char * (this will be set with the units string) + * \return Status code (0=OK, see macro definitions for possible errors) + */ +LIBCOM_API int epicsParseLong(const char *str, long *to, int base, char **units); + +/** + * \brief Convert a string to a unsigned long type + * \copydetails epicsParseLong + */ LIBCOM_API int epicsParseULong(const char *str, unsigned long *to, int base, char **units); + +/** + * \brief Convert a string to a long long type + * \copydetails epicsParseLong + */ LIBCOM_API int epicsParseLLong(const char *str, long long *to, int base, char **units); + +/** + * \brief Convert a string to a unsigned long long type + * \copydetails epicsParseLong + */ LIBCOM_API int epicsParseULLong(const char *str, unsigned long long *to, int base, char **units); + +/** + * \brief Convert a string to a double type + * + * \param str Pointer to a constant character array + * \param to Pointer to the specified type (this will be set during the conversion) + * \param units Pointer to a char * (this will be set with the units string) + * \return Status code (0=OK, see macro definitions for possible errors) + */ LIBCOM_API int epicsParseDouble(const char *str, double *to, char **units); +/** + * \brief Convert a string to a float type + * \copydetails epicsParseDouble + */ LIBCOM_API int epicsParseFloat(const char *str, float *to, char **units); +/** + * \brief Convert a string to an epicsInt8 type + * \copydetails epicsParseLong + */ LIBCOM_API int epicsParseInt8(const char *str, epicsInt8 *to, int base, char **units); + +/** + * \brief Convert a string to an epicsUInt8 type + * \copydetails epicsParseLong + */ LIBCOM_API int epicsParseUInt8(const char *str, epicsUInt8 *to, int base, char **units); + +/** + * \brief Convert a string to an epicsInt16 type + * \copydetails epicsParseLong + */ LIBCOM_API int epicsParseInt16(const char *str, epicsInt16 *to, int base, char **units); + +/** + * \brief Convert a string to an epicsUInt16 type + * \copydetails epicsParseLong + */ LIBCOM_API int epicsParseUInt16(const char *str, epicsUInt16 *to, int base, char **units); +/** + * \brief Convert a string to an epicsInt32 type + * \copydetails epicsParseLong + */ LIBCOM_API int epicsParseInt32(const char *str, epicsInt32 *to, int base, char **units); + +/** + * \brief Convert a string to an epicsUInt32 type + * \copydetails epicsParseLong + */ LIBCOM_API int epicsParseUInt32(const char *str, epicsUInt32 *to, int base, char **units); +/** + * \brief Convert a string to an epicsInt64 type + * \copydetails epicsParseLong + */ LIBCOM_API int epicsParseInt64(const char *str, epicsInt64 *to, int base, char **units); + +/** + * \brief Convert a string to an epicsUInt64 type + * \copydetails epicsParseLong + */ LIBCOM_API int epicsParseUInt64(const char *str, epicsUInt64 *to, int base, char **units); +/** Macro utilizing ::epicsParseFloat to convert */ #define epicsParseFloat32(str, to, units) epicsParseFloat(str, to, units) +/** Macro utilizing ::epicsParseDouble to convert */ #define epicsParseFloat64(str, to, units) epicsParseDouble(str, to, units) /* These macros return 1 if successful, 0 on failure. * This is analagous to the return value from sscanf() */ + +/** + * Macro utilizing ::epicsParseLong to convert + * \return 0=failure, 1=success + */ #define epicsScanLong(str, to, base) (!epicsParseLong(str, to, base, NULL)) + +/** + * Macro utilizing ::epicsParseULong to convert + * \return 0=failure, 1=success + */ #define epicsScanULong(str, to, base) (!epicsParseULong(str, to, base, NULL)) + +/** + * Macro utilizing ::epicsParseLLong to convert + * \return 0=failure, 1=success + */ #define epicsScanLLong(str, to, base) (!epicsParseLLong(str, to, base, NULL)) + +/** + * Macro utilizing ::epicsParseULLong to convert + * \return 0=failure, 1=success + */ #define epicsScanULLong(str, to, base) (!epicsParseULLong(str, to, base, NULL)) + +/** + * Macro utilizing ::epicsParseFloat to convert + * \return 0=failure, 1=success + */ #define epicsScanFloat(str, to) (!epicsParseFloat(str, to, NULL)) + +/** + * Macro utilizing ::epicsParseDouble to convert + * \return 0=failure, 1=success + */ #define epicsScanDouble(str, to) (!epicsParseDouble(str, to, NULL)) #ifdef __cplusplus diff --git a/modules/libcom/src/misc/epicsString.h b/modules/libcom/src/misc/epicsString.h index 3c08e5277..854b552c6 100644 --- a/modules/libcom/src/misc/epicsString.h +++ b/modules/libcom/src/misc/epicsString.h @@ -48,7 +48,7 @@ LIBCOM_API unsigned int epicsMemHash(const char *str, size_t length, * @returns 1.0 when A and B are identical, down to 0.0 when A and B are unrelated, * or < 0.0 on error. * - * @since UNRELEASED + * @since EPICS 7.0.5 */ LIBCOM_API double epicsStrSimilarity(const char *A, const char *B); diff --git a/modules/libcom/src/misc/epicsTypes.h b/modules/libcom/src/misc/epicsTypes.h index 1beb5d2e4..09e550d78 100644 --- a/modules/libcom/src/misc/epicsTypes.h +++ b/modules/libcom/src/misc/epicsTypes.h @@ -8,9 +8,11 @@ * in file LICENSE that is included with this distribution. \*************************************************************************/ -/* - * Author: Jeff Hill - * Date: 5-95 +/** + * \file epicsTypes.h + * \author: Jeff Hill + * + * \brief The core data types used by epics */ #ifndef INC_epicsTypes_H @@ -32,9 +34,12 @@ typedef enum { epicsTrue = 1 } epicsBoolean EPICS_DEPRECATED; -/* +/** + * \name epicsTypes * Architecture Independent Data Types + * * These are sufficient for all our current archs + * @{ */ typedef char epicsInt8; typedef unsigned char epicsUInt8; @@ -49,25 +54,28 @@ typedef epicsUInt16 epicsEnum16; typedef float epicsFloat32; typedef double epicsFloat64; typedef epicsInt32 epicsStatus; + /** @} */ +#define MAX_STRING_SIZE 40 +/** + * \brief !! Dont use this - it may vanish in the future !! + */ typedef struct { unsigned length; char *pString; } epicsString; -/* - * !! Dont use this - it may vanish in the future !! +/** + * \brief !! Dont use this - it may vanish in the future !! * * Provided only for backwards compatibility with * db_access.h - * */ -#define MAX_STRING_SIZE 40 typedef char epicsOldString[MAX_STRING_SIZE]; -/* - * union of all types +/** + * \brief Union of all types * * Strings included here as pointers only so that we support * large string types. @@ -90,11 +98,11 @@ typedef union epics_any { epicsString string; } epicsAny; -/* - * Corresponding Type Codes +/** + * \brief Corresponding Type Codes * (this enum must start at zero) * - * !! Update epicsTypeToDBR_XXXX[] and DBR_XXXXToEpicsType + * !! Update \ref epicsTypeToDBR_XXXX[] and \ref DBR_XXXXToEpicsType * in db_access.h if you edit this enum !! */ typedef enum { @@ -116,8 +124,9 @@ typedef enum { #define invalidEpicsType(x) ((xlastEpicsType)) -/* - * The enumeration "epicsType" is an index to this array +/** + * \brief An array providing the names for each type + * The enumeration \ref epicsType is an index to this array * of type name strings. */ #ifdef epicsTypesGLOBAL @@ -138,8 +147,9 @@ const char *epicsTypeNames [lastEpicsType+1] = { LIBCOM_API extern const char *epicsTypeNames [lastEpicsType+1]; #endif /* epicsTypesGLOBAL */ -/* - * The enumeration "epicsType" is an index to this array +/** + * \brief An array providing the names for each type code + * The enumeration \ref epicsType is an index to this array * of type code name strings. */ #ifdef epicsTypesGLOBAL @@ -160,6 +170,11 @@ const char *epicsTypeCodeNames [lastEpicsType+1] = { LIBCOM_API extern const char *epicsTypeCodeNames [lastEpicsType+1]; #endif /* epicsTypesGLOBAL */ +/** + * \brief An array providing the sizes for each type + * The enumeration \ref epicsType is an index to this array + * of type code name strings. + */ #ifdef epicsTypesGLOBAL const unsigned epicsTypeSizes [lastEpicsType+1] = { sizeof (epicsInt8), @@ -178,10 +193,6 @@ const unsigned epicsTypeSizes [lastEpicsType+1] = { LIBCOM_API extern const unsigned epicsTypeSizes [lastEpicsType+1]; #endif /* epicsTypesGLOBAL */ -/* - * The enumeration "epicsType" is an index to this array - * of type class identifiers. - */ typedef enum { epicsIntC, epicsUIntC, @@ -191,6 +202,11 @@ typedef enum { epicsOldStringC } epicsTypeClass; +/** + * \brief An array providing the class of each type + * The enumeration \ref epicsType is an index to this array + * of type class identifiers. + */ #ifdef epicsTypesGLOBAL const epicsTypeClass epicsTypeClasses [lastEpicsType+1] = { epicsIntC, @@ -209,7 +225,11 @@ const epicsTypeClass epicsTypeClasses [lastEpicsType+1] = { LIBCOM_API extern const epicsTypeClass epicsTypeClasses [lastEpicsType+1]; #endif /* epicsTypesGLOBAL */ - +/** + * \brief An array providing the field name for each type + * The enumeration \ref epicsType is an index to this array + * of type code name strings. + */ #ifdef epicsTypesGLOBAL const char *epicsTypeAnyFieldName [lastEpicsType+1] = { "int8", diff --git a/modules/libcom/src/osi/os/WIN32/osdStrtod.h b/modules/libcom/src/osi/os/WIN32/osdStrtod.h index f7ad78a02..665abf646 100644 --- a/modules/libcom/src/osi/os/WIN32/osdStrtod.h +++ b/modules/libcom/src/osi/os/WIN32/osdStrtod.h @@ -13,11 +13,6 @@ extern "C" { #endif -/* - * epicsStrtod() for systems with broken strtod() routine - */ -LIBCOM_API double epicsStrtod(const char *str, char **endp); - /* * Microsoft apparently added strto[u]ll() in VS2013 * Older compilers have these equivalents though @@ -28,6 +23,19 @@ LIBCOM_API double epicsStrtod(const char *str, char **endp); # define strtoull _strtoui64 #endif +/* + * strtod works in MSVC 1900 and mingw, use + * the OS version in those and our own otherwise + */ +#if (_MSC_VER < 1900) && !defined(_MINGW) +/* + * epicsStrtod() for systems with broken strtod() routine + */ +LIBCOM_API double epicsStrtod(const char *str, char **endp); +#else +# define epicsStrtod strtod +#endif + #ifdef __cplusplus } #endif diff --git a/modules/libcom/src/osi/os/posix/osdMutex.c b/modules/libcom/src/osi/os/posix/osdMutex.c index 83833f85c..18cd08d95 100644 --- a/modules/libcom/src/osi/os/posix/osdMutex.c +++ b/modules/libcom/src/osi/os/posix/osdMutex.c @@ -194,6 +194,7 @@ void epicsMutexOsdShow(struct epicsMutexOSD * pmutex, unsigned int level) void epicsMutexOsdShowAll(void) { +#if defined _POSIX_THREAD_PRIO_INHERIT int proto = -1; int ret = pthread_mutexattr_getprotocol(&globalAttrRecursive, &proto); if(ret) { @@ -201,4 +202,7 @@ void epicsMutexOsdShowAll(void) } else { printf("PI is%s enabled\n", proto==PTHREAD_PRIO_INHERIT ? "" : " not"); } +#else + printf("PI not supported\n"); +#endif } diff --git a/modules/libcom/src/osi/os/vxWorks/osdThread.h b/modules/libcom/src/osi/os/vxWorks/osdThread.h index 259d358e5..25cce37ac 100644 --- a/modules/libcom/src/osi/os/vxWorks/osdThread.h +++ b/modules/libcom/src/osi/os/vxWorks/osdThread.h @@ -1,5 +1,5 @@ /*************************************************************************\ -* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* Copyright (c) 2021 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. @@ -8,13 +8,22 @@ * in file LICENSE that is included with this distribution. \*************************************************************************/ -#ifndef osdThreadh -#define osdThreadh +#ifndef INC_osdThread_H +#define INC_osdThread_H -/* VxWorks 6.9 and later can support joining threads */ +#include -#if (_WRS_VXWORKS_MAJOR == 6 && _WRS_VXWORKS_MINOR < 9) -#undef EPICS_THREAD_CAN_JOIN +#ifdef _WRS_VXWORKS_MAJOR +# define VXWORKS_VERSION_INT VERSION_INT(_WRS_VXWORKS_MAJOR, \ + _WRS_VXWORKS_MINOR, _WRS_VXWORKS_MAINT, _WRS_VXWORKS_SVCPK) +#else +/* Version not available at compile-time, assume... */ +# define VXWORKS_VERSION_INT VERSION_INT(5, 5, 0, 0) #endif -#endif /* osdThreadh */ +#if VXWORKS_VERSION_INT < VERSION_INT(6, 9, 4, 1) +/* VxWorks 6.9.4.1 and later can support joining threads */ +# undef EPICS_THREAD_CAN_JOIN +#endif + +#endif /* INC_osdThread_H */ diff --git a/modules/libcom/src/osi/osiClockTime.c b/modules/libcom/src/osi/osiClockTime.c index 5368898d7..8b9dfd77b 100644 --- a/modules/libcom/src/osi/osiClockTime.c +++ b/modules/libcom/src/osi/osiClockTime.c @@ -58,14 +58,26 @@ static void ClockTimeSync(void *dummy); /* ClockTime_Report iocsh command */ static const iocshArg ReportArg0 = { "interest_level", iocshArgArgv}; static const iocshArg * const ReportArgs[1] = { &ReportArg0 }; -static const iocshFuncDef ReportFuncDef = {"ClockTime_Report", 1, ReportArgs}; +static const iocshFuncDef ReportFuncDef = {"ClockTime_Report", 1, ReportArgs, + "Reports clock synchronization status:\n" + " - On vxWorks and RTEMS:\n" + " * synchronization state\n" + " * last synchronization time with provider\n" + " * synchronization interval\n" + " - On workstation (WIN,*NIX):\n" + " * minimal report\n"}; static void ReportCallFunc(const iocshArgBuf *args) { ClockTime_Report(args[0].ival); } /* ClockTime_Shutdown iocsh command */ -static const iocshFuncDef ShutdownFuncDef = {"ClockTime_Shutdown", 0, NULL}; +static const iocshFuncDef ShutdownFuncDef = {"ClockTime_Shutdown", 0, NULL, + "Stops the OS synchronization thread\n" + " - On vxWorks and RTEMS:\n" + " * OS clock will free run\n" + " - On workstation (WIN,*NIX):\n" + " * no change\n"}; static void ShutdownCallFunc(const iocshArgBuf *args) { ClockTime_Shutdown(NULL); diff --git a/modules/libcom/src/osi/osiNTPTime.c b/modules/libcom/src/osi/osiNTPTime.c index fa5217faf..bd9821e4c 100644 --- a/modules/libcom/src/osi/osiNTPTime.c +++ b/modules/libcom/src/osi/osiNTPTime.c @@ -64,14 +64,21 @@ static void NTPTimeSync(void *dummy); /* NTPTime_Report iocsh command */ static const iocshArg ReportArg0 = { "interest_level", iocshArgArgv}; static const iocshArg * const ReportArgs[1] = { &ReportArg0 }; -static const iocshFuncDef ReportFuncDef = {"NTPTime_Report", 1, ReportArgs}; +static const iocshFuncDef ReportFuncDef = {"NTPTime_Report", 1, ReportArgs, + "Display time provider synchronization state\n" + " interest_level - with level 1 it also shows:\n" + " * synchronization interval\n" + " * time when last synchronized\n" + " * nominal and measured system tick rates\n" + " * server address (vxWorks only)\n"}; static void ReportCallFunc(const iocshArgBuf *args) { NTPTime_Report(args[0].ival); } /* NTPTime_Shutdown iocsh command */ -static const iocshFuncDef ShutdownFuncDef = {"NTPTime_Shutdown", 0, NULL}; +static const iocshFuncDef ShutdownFuncDef = {"NTPTime_Shutdown", 0, NULL, + "Shuts down NTP time synchronization thread\n"}; static void ShutdownCallFunc(const iocshArgBuf *args) { NTPTime_Shutdown(NULL); diff --git a/modules/libcom/src/yajl/yajl_parser.c b/modules/libcom/src/yajl/yajl_parser.c index b400709a6..b134228d2 100644 --- a/modules/libcom/src/yajl/yajl_parser.c +++ b/modules/libcom/src/yajl/yajl_parser.c @@ -29,6 +29,8 @@ #include "yajl_encode.h" #include "yajl_bytestack.h" +#include + #ifndef LLONG_MAX #define LLONG_MAX 0x7FFFFFFFFFFFFFFFLL #define LLONG_MIN (-0x7FFFFFFFFFFFFFFFLL - 1) @@ -334,7 +336,7 @@ yajl_do_parse(yajl_handle hand, const unsigned char * jsonText, yajl_buf_clear(hand->decodeBuf); yajl_buf_append(hand->decodeBuf, buf, bufLen); buf = yajl_buf_data(hand->decodeBuf); - d = strtod((char *) buf, NULL); + d = epicsStrtod((char *) buf, NULL); if ((d == HUGE_VAL || d == -HUGE_VAL) && errno == ERANGE) { diff --git a/modules/libcom/test/Makefile b/modules/libcom/test/Makefile index 3ad869afb..ef8605002 100755 --- a/modules/libcom/test/Makefile +++ b/modules/libcom/test/Makefile @@ -337,3 +337,10 @@ include $(TOP)/configure/RULES rtemsTestData.c : $(TESTFILES) $(TOOLS)/epicsMakeMemFs.pl $(PERL) $(TOOLS)/epicsMakeMemFs.pl $@ epicsRtemsFSImage $(TESTFILES) + +epicsLoadTest$(DEP): epicsInstallDir.h + +# use INSTALL_LOCATION instead of FINAL_LOCATION since test executables are not installed. +epicsInstallDir.h: $(TOP)/configure/CONFIG_SITE* + $(ECHO) "INSTALL_LOCATION=$(INSTALL_LOCATION)" + $(PERL) $(TOP)/modules/database/src/std/softIoc/makeInstallDir.pl "$(INSTALL_LOCATION)" > $@ diff --git a/modules/libcom/test/epicsLoadTest.cpp b/modules/libcom/test/epicsLoadTest.cpp index 304c42d73..3ffc89949 100644 --- a/modules/libcom/test/epicsLoadTest.cpp +++ b/modules/libcom/test/epicsLoadTest.cpp @@ -16,6 +16,8 @@ #include "epicsFindSymbol.h" #include "epicsThread.h" +#include "epicsInstallDir.h" + namespace { void loadBad() @@ -52,9 +54,9 @@ void loadCA() std::ostringstream strm; // running in eg. modules/libcom/test/O.linux-x86_64-debug #ifdef _WIN32 - strm<<"..\\..\\..\\..\\bin\\"<unicode_escape_url($section); - $section = '_' unless length $section; - return $section; + my($self, $t) = @_; + for ($t) { + s/<[^>]+>//g; # Strip HTML. + s/&[^;]+;//g; # Strip entities. + s/^\s+//; s/\s+$//; # Strip white space. + s/^([^a-zA-Z]+)$/pod$1/; # Prepend "pod" if no valid chars. + s/^[^a-zA-Z]+//; # First char must be a letter. + s/[^-a-zA-Z0-9_:.]+/-/g; # All other chars must be valid. + s/[-:.]+$//; # Strip trailing punctuation. + } + return $t; } 1;