683 lines
29 KiB
C++
683 lines
29 KiB
C++
/*
|
|
* Copyright - See the COPYRIGHT that is included with this distribution.
|
|
* pvxs is distributed subject to a Software License Agreement found
|
|
* in file LICENSE that is included with this distribution.
|
|
*
|
|
* Author George S. McIntyre <george@level-n.com>, 2023
|
|
*
|
|
*/
|
|
|
|
#include <string>
|
|
#include <algorithm>
|
|
|
|
#include <special.h>
|
|
#include <epicsTime.h>
|
|
|
|
#include "iocsource.h"
|
|
#include "dbentry.h"
|
|
#include "dberrormessage.h"
|
|
#include "typeutils.h"
|
|
#include "credentials.h"
|
|
#include "securityclient.h"
|
|
#include "securitylogger.h"
|
|
|
|
namespace pvxs {
|
|
namespace ioc {
|
|
|
|
/**
|
|
* IOC function that will get data from the database. This will use the provided value prototype to determine the shape
|
|
* of the data to be returned (if it has a `value` subfield, it is a structure). The provided channel
|
|
* is used to retrieve the data and the flags forValues and forProperties are used to determine whether to fetch
|
|
* values, properties or both from the database.
|
|
*
|
|
* When the data has been retrieved the provided returnFn is called with the value, otherwise the an
|
|
* exception is thrown
|
|
*
|
|
* @param pDbValueChannel the channel to get the value from
|
|
* @param pDbPropertiesChannel the channel to get the properties from
|
|
* @param valuePrototype the value prototype to use to determine the shape of the data
|
|
* @param getOperationType for values, for properties or for metadata
|
|
* @param pDbFieldLog the field log of changes if this comes from a subscription
|
|
*/
|
|
void IOCSource::get(dbChannel* pDbValueChannel, dbChannel* pDbPropertiesChannel, Value& valuePrototype,
|
|
const GetOperationType getOperationType,
|
|
db_field_log* pDbFieldLog) {
|
|
// Assumes this is the leaf node in a group, or is a simple db record.field reference
|
|
Value value = valuePrototype; // The value that will be returned, if compound then metadata is set here
|
|
Value valueTarget = valuePrototype; // The part of value that will be retrieved from the database value field
|
|
bool isCompound = false;
|
|
if (getOperationType <= FOR_VALUE && value.type() == TypeCode::Any) {
|
|
auto type = fromDbrType(dbChannelFinalFieldType(pDbValueChannel));
|
|
if (dbChannelFinalElements(pDbValueChannel) != 1) {
|
|
type = type.arrayOf();
|
|
}
|
|
value = valueTarget = TypeDef(type).create();
|
|
valuePrototype.from(value);
|
|
} else if (auto targetCandidate = value["value"]) {
|
|
isCompound = true;
|
|
valueTarget = targetCandidate;
|
|
}
|
|
|
|
// options bit mask LSB to MSB
|
|
uint32_t options = 0;
|
|
if (isCompound && getOperationType <= FOR_METADATA) {
|
|
options = IOC_VALUE_OPTIONS;
|
|
}
|
|
if (isCompound && (getOperationType == FOR_VALUE_AND_PROPERTIES ||
|
|
getOperationType == FOR_PROPERTIES)) {
|
|
options |= IOC_PROPERTIES_OPTIONS;
|
|
}
|
|
|
|
if (dbChannelFinalElements(pDbValueChannel) == 1) {
|
|
getScalar(pDbValueChannel, pDbPropertiesChannel, value, valueTarget, options, getOperationType, pDbFieldLog);
|
|
} else {
|
|
getArray(pDbValueChannel, pDbPropertiesChannel, value, valueTarget, options, getOperationType, pDbFieldLog);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a scalar value from the database
|
|
* @param pDbValueChannel the database channel to get the value from
|
|
* @param pDbPropertiesChannel the database channel to get the properties from
|
|
* @param value the value to set including metadata if this is a compound value
|
|
* @param valueTarget where to store the "value" part of the scalar within value
|
|
* @param requestedOptions the options defining what metadata to get
|
|
* @param getOperationType for values, for properties or for metadata
|
|
* @param pDbFieldLog the field log of changes if this comes from a subscription
|
|
*/
|
|
void IOCSource::getScalar(dbChannel* pDbValueChannel, dbChannel* pDbPropertiesChannel, Value& value, Value& valueTarget,
|
|
uint32_t& requestedOptions, const GetOperationType getOperationType, db_field_log* pDbFieldLog) {
|
|
ValueBuffer valueBuffer{}; // Enough for metadata and 1 scalar
|
|
void* pValueBuffer = &valueBuffer;
|
|
long nElements = (getOperationType <= FOR_VALUE) ? 1 : 0;
|
|
long actualOptions = requestedOptions;
|
|
|
|
// Get metadata/properties and field value
|
|
// Note that metadata will precede the value in the buffer and will be laid out in the options order
|
|
DBErrorMessage dbErrorMessage;
|
|
if (getOperationType <= FOR_METADATA) {
|
|
dbErrorMessage =
|
|
dbChannelGet(pDbValueChannel, dbChannelFinalFieldType(pDbValueChannel), pValueBuffer, &actualOptions,
|
|
&nElements,
|
|
pDbFieldLog);
|
|
} else {
|
|
dbErrorMessage =
|
|
dbChannelGet(pDbPropertiesChannel, dbChannelFinalFieldType(pDbPropertiesChannel), pValueBuffer,
|
|
&actualOptions,
|
|
&nElements,
|
|
pDbFieldLog);
|
|
}
|
|
|
|
if (dbErrorMessage) {
|
|
throw std::runtime_error(dbErrorMessage.c_str());
|
|
}
|
|
|
|
// Get metadata/properties from buffer if any have been requested
|
|
getMetadata(value, pValueBuffer, requestedOptions, actualOptions);
|
|
|
|
// Get the value if it has been requested
|
|
if (getOperationType <= FOR_VALUE) {
|
|
if (dbChannelFinalFieldType(pDbValueChannel) == DBR_ENUM && valueTarget.type() == TypeCode::Struct) {
|
|
valueTarget["index"] = *(uint16_t*)(pValueBuffer);
|
|
} else {
|
|
getValueFromBuffer(valueTarget, pValueBuffer);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get an array value from the database
|
|
*
|
|
* @param pDbValueChannel the database channel to get the value from
|
|
* @param pDbPropertiesChannel the database channel to get the properties from
|
|
* @param value the value to set including metadata if this is a compound value
|
|
* @param valueTarget where to store the "value" part of the array within value
|
|
* @param requestedOptions the options defining what metadata to get
|
|
* @param getOperationType for values, for properties or for metadata
|
|
* @param pDbFieldLog the field log of changes if this comes from a subscription
|
|
*/
|
|
void IOCSource::getArray(dbChannel* pDbValueChannel, dbChannel* pDbPropertiesChannel, Value& value, Value& valueTarget,
|
|
uint32_t& requestedOptions, const GetOperationType getOperationType, db_field_log* pDbFieldLog) {
|
|
// value buffer to store the field we will get from the database including metadata.
|
|
std::vector<char> valueBuffer;
|
|
auto nElements = (getOperationType <= FOR_VALUE) ? (long)dbChannelFinalElements(pDbValueChannel)
|
|
: 0; // maximal number of elements
|
|
// Initialize the buffer to the maximal size including metadata and zero it out
|
|
valueBuffer.resize(nElements * pDbValueChannel->addr.field_size + MAX_METADATA_SIZE, '\0');
|
|
void* pValueBuffer = &valueBuffer[0];
|
|
long actualOptions = requestedOptions;
|
|
|
|
// Get the metadata/properties and value into this buffer
|
|
// Note that metadata will precede the array value in the buffer and will be laid out in the options order
|
|
DBErrorMessage dbErrorMessage;
|
|
if (getOperationType <= FOR_METADATA) {
|
|
dbErrorMessage =
|
|
dbChannelGet(pDbValueChannel, dbChannelFinalFieldType(pDbValueChannel), pValueBuffer, &actualOptions,
|
|
&nElements,
|
|
pDbFieldLog);
|
|
} else {
|
|
dbErrorMessage =
|
|
dbChannelGet(pDbPropertiesChannel, pDbPropertiesChannel->final_type, pValueBuffer, &actualOptions,
|
|
&nElements,
|
|
pDbFieldLog);
|
|
}
|
|
|
|
if (dbErrorMessage) {
|
|
throw std::runtime_error(dbErrorMessage.c_str());
|
|
}
|
|
|
|
// Get metadata/properties from buffer if any have been requested
|
|
getMetadata(value, pValueBuffer, requestedOptions, actualOptions);
|
|
|
|
// Get the value array if it has been requested
|
|
if (getOperationType <= FOR_VALUE) {
|
|
// Get the array value from the updated buffer pointer
|
|
// Note: nElements will have been updated with the number of actual elements in the array
|
|
if (dbChannelFinalFieldType(pDbValueChannel) == DBR_ENUM && valueTarget.type() == TypeCode::Struct) {
|
|
shared_array<uint16_t> values(nElements);
|
|
for (auto i = 0; i < nElements; i++) {
|
|
values[i] = ((uint16_t*)pValueBuffer)[i];
|
|
}
|
|
valueTarget["index"] = values.freeze();
|
|
} else {
|
|
getValueFromBuffer(valueTarget, pValueBuffer, nElements);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Put a given value to the specified channel. Throw an exception if there are any errors.
|
|
*
|
|
* @param pDbChannel the channel to put the value into
|
|
* @param value the value to put
|
|
*/
|
|
void IOCSource::put(dbChannel* pDbChannel, const Value& value) {
|
|
Value valueSource = value;
|
|
// TODO may need to handle Array of and Union as special cases as well
|
|
if (value.type() == TypeCode::Any) {
|
|
valueSource = valueSource["->"];
|
|
} else if (auto sourceCandidate = value["value"]) {
|
|
valueSource = sourceCandidate;
|
|
}
|
|
|
|
if (dbChannelFinalElements(pDbChannel) == 1) {
|
|
putScalar(pDbChannel, valueSource);
|
|
} else {
|
|
putArray(pDbChannel, valueSource);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Put a given scalar value to the specified channel. Throw an exception if there are any errors.
|
|
*
|
|
* @param pDbChannel the channel to put the value into
|
|
* @param value the scalar value to put
|
|
*/
|
|
void IOCSource::putScalar(dbChannel* pDbChannel, const Value& value) {
|
|
ScalarValueBuffer valueBuffer{};
|
|
auto pValueBuffer = (char*)&valueBuffer;
|
|
|
|
if (dbChannelFinalFieldType(pDbChannel) == DBR_ENUM) {
|
|
*(uint16_t*)(pValueBuffer) = (value)["index"].as<uint16_t>();
|
|
} else {
|
|
setValueInBuffer(value, pValueBuffer, pDbChannel);
|
|
}
|
|
|
|
long status;
|
|
if (dbChannelFieldType(pDbChannel) >= DBF_INLINK && dbChannelFieldType(pDbChannel) <= DBF_FWDLINK) {
|
|
status = dbChannelPutField(pDbChannel, dbChannelFinalFieldType(pDbChannel), pValueBuffer, 1);
|
|
} else {
|
|
status = dbChannelPut(pDbChannel, dbChannelFinalFieldType(pDbChannel), pValueBuffer, 1);
|
|
}
|
|
DBErrorMessage dbErrorMessage(status);
|
|
if (dbErrorMessage) {
|
|
throw std::runtime_error(dbErrorMessage.c_str());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Put a given array value to the specified channel. Throw an exception if there are any errors.
|
|
*
|
|
* @param pDbChannel the channel to put the value into
|
|
* @param value the array value to put
|
|
*/
|
|
void IOCSource::putArray(dbChannel* pDbChannel, const Value& value) {
|
|
auto valueArray = value.as<shared_array<const void>>();
|
|
|
|
void* pValueBuffer;
|
|
long nElements = (long)valueArray.size();
|
|
std::vector<char> stringValueBuffer;
|
|
|
|
if (dbChannelFinalFieldType(pDbChannel) == DBR_STRING) {
|
|
stringValueBuffer.resize(MAX_STRING_SIZE * valueArray.size(), '\0');
|
|
char* pCurrent = stringValueBuffer.data();
|
|
auto stringArray = valueArray.castTo<const std::string>();
|
|
for (auto& element: stringArray) {
|
|
element.copy(pCurrent, MAX_STRING_SIZE - 1);
|
|
pCurrent += MAX_STRING_SIZE;
|
|
}
|
|
pValueBuffer = stringValueBuffer.data();
|
|
} else {
|
|
// Set the buffer to the internal value buffer as it's the same for the db records for non-string fields
|
|
pValueBuffer = (void*)valueArray.data();
|
|
setValueInBuffer(value, (char*)pValueBuffer, nElements);
|
|
}
|
|
|
|
long status;
|
|
if (dbChannelFieldType(pDbChannel) >= DBF_INLINK && dbChannelFieldType(pDbChannel) <= DBF_FWDLINK) {
|
|
status = dbChannelPutField(pDbChannel, dbChannelFinalFieldType(pDbChannel), pValueBuffer, nElements);
|
|
} else {
|
|
status = dbChannelPut(pDbChannel, dbChannelFinalFieldType(pDbChannel), pValueBuffer, nElements);
|
|
}
|
|
DBErrorMessage dbErrorMessage(status);
|
|
if (dbErrorMessage) {
|
|
throw std::runtime_error(dbErrorMessage.c_str());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Do necessary preprocessing before put operations. Check if put is allowed.
|
|
*
|
|
* @param pDbChannel channel to do preprocessing for
|
|
* @param securityLogger the logger that will audit security events
|
|
* @param credentials client credentials that are applied to this execution context
|
|
* @param securityClient the security client. Keep in scope around the put operation
|
|
*/
|
|
void
|
|
IOCSource::doPreProcessing(dbChannel* pDbChannel, SecurityLogger& securityLogger, const Credentials& credentials,
|
|
const SecurityClient& securityClient) {
|
|
if (pDbChannel->addr.special == SPC_ATTRIBUTE) {
|
|
throw std::runtime_error("Unable to put value: Modifications not allowed: S_db_noMod");
|
|
} else if (pDbChannel->addr.precord->disp && pDbChannel->addr.pfield != &pDbChannel->addr.precord->disp) {
|
|
throw std::runtime_error("Unable to put value: Field Disabled: S_db_putDisabled");
|
|
}
|
|
|
|
SecurityLogger asWritePvt(
|
|
asTrapWriteWithData((securityClient.cli)[0], // The user is the first element
|
|
credentials.cred[0].c_str(), // The user is the first element
|
|
credentials.host.c_str(),
|
|
pDbChannel,
|
|
dbChannelFinalFieldType(pDbChannel),
|
|
dbChannelFinalElements(pDbChannel),
|
|
nullptr
|
|
)
|
|
);
|
|
|
|
securityLogger.swap(asWritePvt);
|
|
|
|
}
|
|
|
|
/**
|
|
* Do necessary preprocessing before put operations. Check if put is allowed.
|
|
*
|
|
* @param securityClient security client applied to this execution context
|
|
*/
|
|
void IOCSource::doFieldPreProcessing(const SecurityClient& securityClient) {
|
|
if (!securityClient.canWrite()) {
|
|
// TODO this will abort the whole group put operation, so may be a behavior change, need to check
|
|
throw std::runtime_error("Put not permitted");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Do necessary post processing after put operations. If this field is a processing record then do processing
|
|
* and set status
|
|
* Note: Only called when dbPutField() is not called.
|
|
*
|
|
* @param pDbChannel channel to do post processing for
|
|
* @param forceProcessing whether to force processing, True, False
|
|
*/
|
|
void IOCSource::doPostProcessing(dbChannel* pDbChannel, TriState forceProcessing) {
|
|
if (pDbChannel->addr.pfield == &pDbChannel->addr.precord->proc ||
|
|
(forceProcessing == True) ||
|
|
(pDbChannel->addr.pfldDes->process_passive &&
|
|
pDbChannel->addr.precord->scan == 0 &&
|
|
dbChannelFinalFieldSize(pDbChannel) < DBR_PUT_ACKT &&
|
|
forceProcessing == Unset)) {
|
|
if (pDbChannel->addr.precord->pact) {
|
|
if (dbAccessDebugPUTF && pDbChannel->addr.precord->tpro) {
|
|
printf("%s: single source onPut to Active '%s', setting RPRO=1\n",
|
|
epicsThreadGetNameSelf(), pDbChannel->addr.precord->name);
|
|
}
|
|
pDbChannel->addr.precord->rpro = TRUE;
|
|
} else {
|
|
pDbChannel->addr.precord->putf = TRUE;
|
|
DBErrorMessage dbErrorMessage(dbProcess(pDbChannel->addr.precord));
|
|
if (dbErrorMessage) {
|
|
throw std::runtime_error(dbErrorMessage.c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set a flag that will force processing of record in the specified security control object
|
|
*
|
|
* @param pvRequest the request
|
|
* @param securityControlObject the security control object to update
|
|
*/
|
|
void IOCSource::setForceProcessingFlag(const Value& pvRequest,
|
|
const std::shared_ptr<SecurityControlObject>& securityControlObject) {
|
|
pvRequest["record._options.process"]
|
|
.as<std::string>([&securityControlObject](const std::string& forceProcessingOption) {
|
|
if (forceProcessingOption == "true") {
|
|
securityControlObject->forceProcessing = True;
|
|
} else if (forceProcessingOption == "false") {
|
|
securityControlObject->forceProcessing = False;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Set a return value from the given database value buffer
|
|
*
|
|
* @param valueTarget the value to set
|
|
* @param pValueBuffer pointer to the database value buffer
|
|
*/
|
|
void IOCSource::getValueFromBuffer(Value& valueTarget, const void* pValueBuffer) {
|
|
auto valueType(valueTarget.type());
|
|
|
|
if (valueType == TypeCode::String) {
|
|
valueTarget = ((const char*)pValueBuffer);
|
|
} else {
|
|
SwitchTypeCodeForTemplatedCall(valueType, getValueFromBuffer, (valueTarget, pValueBuffer));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set a return value from the given database value buffer. This is the array version of the function
|
|
*
|
|
* @param valueTarget the value to set
|
|
* @param pValueBuffer the database value buffer
|
|
* @param nElements the number of elements in the buffer
|
|
*/
|
|
void IOCSource::getValueFromBuffer(Value& valueTarget, const void* pValueBuffer, const long& nElements) {
|
|
auto valueType(valueTarget.type());
|
|
if (valueType == TypeCode::StringA) {
|
|
shared_array<std::string> values(nElements);
|
|
char stringBuffer[MAX_STRING_SIZE + 1]{ 0 }; // Need to do this because some strings may not be null terminated
|
|
for (auto i = 0; i < nElements; i++) {
|
|
auto pStringValue = (char*)&((const char*)pValueBuffer)[i * MAX_STRING_SIZE];
|
|
strncpy(stringBuffer, pStringValue, MAX_STRING_SIZE);
|
|
values[i] = (char*)&stringBuffer[0];
|
|
}
|
|
valueTarget = values.freeze().template castTo<const void>();
|
|
} else {
|
|
SwitchTypeCodeForTemplatedCall(valueType, getValueFromBuffer, (valueTarget, pValueBuffer, nElements));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set scalar value into given database buffer
|
|
*
|
|
* @param valueSource the value to put into the buffer
|
|
* @param pValueBuffer the database buffer to put it in
|
|
* @param pDbChannel the db channel
|
|
*/
|
|
void IOCSource::setValueInBuffer(const Value& valueSource, char* pValueBuffer, dbChannel* pDbChannel) {
|
|
auto valueType(valueSource.type());
|
|
if (valueType == TypeCode::String) {
|
|
setStringValueInBuffer(valueSource, pValueBuffer);
|
|
} else if (valueType == TypeCode::Any || valueType == TypeCode::Union) {
|
|
SwitchTypeCodeForTemplatedCall(fromDbrType(dbChannelFinalFieldType(pDbChannel)), setValueInBuffer,
|
|
(valueSource, pValueBuffer));
|
|
} else {
|
|
SwitchTypeCodeForTemplatedCall(valueType, setValueInBuffer, (valueSource, pValueBuffer));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set an array value in the given buffer
|
|
*
|
|
* @param valueSource the value to put into the buffer
|
|
* @param pValueBuffer the database buffer to put it in
|
|
* @param nElements the number of elements to put into the buffer
|
|
*/
|
|
void IOCSource::setValueInBuffer(const Value& valueSource, char* pValueBuffer, long nElements) {
|
|
auto valueType(valueSource.type());
|
|
if (valueType == TypeCode::StringA) {
|
|
auto sharedValueArray = valueSource.as<shared_array<const Value>>();
|
|
for (auto i = 0u; i < sharedValueArray.size(); i++, pValueBuffer += MAX_STRING_SIZE) {
|
|
setStringValueInBuffer(sharedValueArray[i], pValueBuffer);
|
|
}
|
|
} else {
|
|
SwitchTypeCodeForTemplatedCall(valueType, setValueInBuffer, (valueSource, pValueBuffer, nElements));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Given a string value source, and a buffer, copy the string contents into the buffer up to the MAX_STRING_SIZE.
|
|
* Null terminate the string before exiting.
|
|
*
|
|
* @param valueSource the string value source
|
|
* @param pValueBuffer the buffer to copy the string contents to
|
|
*/
|
|
void IOCSource::setStringValueInBuffer(const Value& valueSource, char* pValueBuffer) {
|
|
auto stringValue = valueSource.as<std::string>();
|
|
auto len = std::min(stringValue.length(), (size_t)MAX_STRING_SIZE - 1);
|
|
stringValue.copy(pValueBuffer, len);
|
|
pValueBuffer[len] = '\0';
|
|
}
|
|
|
|
/**
|
|
* Set the value field of the given return value to an array of scalars pointed to by pValueBuffer
|
|
* Supported types are:
|
|
* TypeCode::Int8 TypeCode::UInt8
|
|
* TypeCode::Int16 TypeCode::UInt16
|
|
* TypeCode::Int32 TypeCode::UInt32
|
|
* TypeCode::Int64 TypeCode::UInt64
|
|
* TypeCode::Float32 TypeCode::Float64
|
|
*
|
|
* @tparam valueType the type of the scalars stored in this array. One of the supported types
|
|
* @param valueTarget the return value
|
|
* @param pValueBuffer the pointer to the data containing the database data to store in the return value
|
|
* @param nElements the number of elements in the array
|
|
*/
|
|
template<typename valueType>
|
|
void IOCSource::getValueFromBuffer(Value& valueTarget, const void* pValueBuffer, const long& nElements) {
|
|
shared_array<valueType> values(nElements);
|
|
for (auto i = 0; i < nElements; i++) {
|
|
values[i] = ((valueType*)pValueBuffer)[i];
|
|
}
|
|
valueTarget = values.freeze().template castTo<const void>();
|
|
}
|
|
|
|
/**
|
|
* Get the value into the given database value buffer (templated)
|
|
*
|
|
* @tparam valueType the type of the scalars stored in this array. One of the supported types
|
|
* @param valueSource the value to put into the buffer
|
|
* @param pValueBuffer the database buffer to put it in
|
|
* @param nElements the number of elements to put into the buffer
|
|
*/
|
|
template<typename valueType>
|
|
void IOCSource::setValueInBuffer(const Value& valueSource, void* pValueBuffer, long nElements) {
|
|
auto valueArray = valueSource.as<shared_array<const valueType>>();
|
|
for (auto i = 0; i < nElements; i++) {
|
|
((valueType*)pValueBuffer)[i] = valueArray[i];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Utility function to get the TypeCode that the given database channel is configured for
|
|
*
|
|
* @param pDbChannel the pointer to the database channel to get the TypeCode for
|
|
* @param errOnLinks determines whether to throw an error on finding links, default no
|
|
* @return the TypeCode that the channel is configured for
|
|
*/
|
|
TypeCode IOCSource::getChannelValueType(const dbChannel* pDbChannel, const bool errOnLinks) {
|
|
auto dbChannel(pDbChannel);
|
|
short dbrType(dbChannelFinalFieldType(dbChannel));
|
|
auto nFinalElements(dbChannelFinalElements(dbChannel));
|
|
auto nElements(dbChannelElements(dbChannel));
|
|
|
|
TypeCode valueType;
|
|
|
|
if (dbChannelFieldType(dbChannel) == DBF_STRING && nElements == 1 && dbrType && nFinalElements > 1) {
|
|
// single character long DBF_STRING being cast to DBF_CHAR array.
|
|
valueType = TypeCode::String;
|
|
|
|
} else {
|
|
if (dbrType == DBF_INLINK || dbrType == DBF_OUTLINK || dbrType == DBF_FWDLINK) {
|
|
if (errOnLinks) {
|
|
throw std::runtime_error("Link fields not allowed in this context");
|
|
} else {
|
|
// Handle as chars and fail later
|
|
dbrType = DBF_CHAR;
|
|
}
|
|
}
|
|
|
|
valueType = fromDbfType(dbfType(dbrType));
|
|
if (valueType != TypeCode::Null && nFinalElements != 1) {
|
|
valueType = valueType.arrayOf();
|
|
}
|
|
}
|
|
return valueType;
|
|
}
|
|
|
|
/**
|
|
* Get Metadata from the given buffer into the provided value object. The options parameter is used
|
|
* to select the metadata to retrieve. It must always be retrieved in the specified order
|
|
* as it is laid out that way by the db subsystems on retrieval.
|
|
*
|
|
* @param value the value object to retrieve the metadata into
|
|
* @param pValueBuffer the db value buffer retrieved from the db subsystem
|
|
* @param options the options parameter used to select the metadata.
|
|
*/
|
|
void IOCSource::getMetadata(Value& value, void*& pValueBuffer, const uint32_t& requestedOptions,
|
|
const uint32_t& actualOptions) {
|
|
if (requestedOptions) {
|
|
// Temporary variable to store metadata while retrieving it
|
|
Metadata metadata;
|
|
|
|
// Alarm
|
|
if (requestedOptions & DBR_STATUS) {
|
|
get4MetadataFields(pValueBuffer, uint16_t,
|
|
metadata.metadata.status, metadata.metadata.severity,
|
|
metadata.metadata.acks, metadata.metadata.ackt);
|
|
if (actualOptions & DBR_STATUS) {
|
|
checkedSetField(metadata.metadata.status, alarm.status);
|
|
checkedSetField(metadata.metadata.severity, alarm.severity);
|
|
checkedSetField(metadata.metadata.acks, alarm.acks);
|
|
checkedSetField(metadata.metadata.ackt, alarm.ackt);
|
|
}
|
|
}
|
|
|
|
// Alarm message
|
|
if (requestedOptions & DBR_AMSG) {
|
|
getMetadataString(pValueBuffer, metadata.metadata.amsg);
|
|
if (actualOptions & DBR_AMSG) {
|
|
checkedSetStringField(metadata.metadata.amsg, alarm.message);
|
|
}
|
|
}
|
|
|
|
// Units
|
|
if (requestedOptions & DBR_UNITS) {
|
|
getMetadataBuffer(pValueBuffer, const char, metadata.pUnits, DB_UNITS_SIZE);
|
|
if (actualOptions & DBR_UNITS && value["display"]) {
|
|
checkedSetStringField(metadata.pUnits, display.units);
|
|
}
|
|
}
|
|
|
|
// Precision
|
|
if (requestedOptions & DBR_PRECISION) {
|
|
getMetadataBuffer(pValueBuffer, const dbr_precision, metadata.pPrecision, dbr_precision_size);
|
|
if (actualOptions & DBR_PRECISION && value["display"]) {
|
|
checkedSetField(metadata.pPrecision->precision.dp, display.precision);
|
|
}
|
|
}
|
|
|
|
// Time
|
|
if (requestedOptions & DBR_TIME) {
|
|
get2MetadataFields(pValueBuffer, uint32_t, metadata.metadata.time.secPastEpoch,
|
|
metadata.metadata.time.nsec);
|
|
if (actualOptions & DBR_TIME) {
|
|
checkedSetField(metadata.metadata.time.secPastEpoch + POSIX_TIME_AT_EPICS_EPOCH,
|
|
timeStamp.secondsPastEpoch);
|
|
checkedSetField(metadata.metadata.time.nsec, timeStamp.nanoseconds);
|
|
}
|
|
}
|
|
|
|
// User tag
|
|
if (requestedOptions & DBR_UTAG) {
|
|
getMetadataField(pValueBuffer, uint64_t, metadata.metadata.utag);
|
|
if (actualOptions & DBR_UTAG) {
|
|
checkedSetField(metadata.metadata.utag, timeStamp.userTag);
|
|
}
|
|
}
|
|
|
|
// Enum strings
|
|
if (requestedOptions & DBR_ENUM_STRS) {
|
|
getMetadataBuffer(pValueBuffer, const dbr_enumStrs, metadata.enumStrings, dbr_enumStrs_size);
|
|
if (actualOptions & DBR_ENUM_STRS && value["value.choices"] && metadata.enumStrings) {
|
|
shared_array<std::string> choices(metadata.enumStrings->no_str);
|
|
for (epicsUInt32 i = 0; i < metadata.enumStrings->no_str; i++) {
|
|
choices[i] = metadata.enumStrings->strs[i];
|
|
}
|
|
value["value.choices"] = choices.freeze().castTo<const void>();
|
|
}
|
|
}
|
|
|
|
// Display long
|
|
if (requestedOptions & DBR_GR_LONG) {
|
|
getMetadataBuffer(pValueBuffer, const struct dbr_grLong, metadata.graphicsLong, dbr_grLong_size);
|
|
if (actualOptions & DBR_GR_LONG && value["display"]) {
|
|
checkedSetLongField(metadata.graphicsLong->lower_disp_limit, display.limitLow);
|
|
checkedSetLongField(metadata.graphicsLong->upper_disp_limit, display.limitHigh);
|
|
}
|
|
}
|
|
|
|
// Display double
|
|
if (requestedOptions & DBR_GR_DOUBLE) {
|
|
getMetadataBuffer(pValueBuffer, const struct dbr_grDouble, metadata.graphicsDouble, dbr_grDouble_size);
|
|
if (actualOptions & DBR_GR_DOUBLE && value["display"]) {
|
|
checkedSetDoubleField(metadata.graphicsDouble->lower_disp_limit, display.limitLow);
|
|
checkedSetDoubleField(metadata.graphicsDouble->upper_disp_limit, display.limitHigh);
|
|
}
|
|
}
|
|
|
|
// Control long
|
|
if (requestedOptions & DBR_CTRL_LONG) {
|
|
getMetadataBuffer(pValueBuffer, const struct dbr_ctrlLong, metadata.controlLong, dbr_ctrlLong_size);
|
|
if (actualOptions & DBR_CTRL_LONG && value["control"]) {
|
|
checkedSetLongField(metadata.controlLong->lower_ctrl_limit, control.limitLow);
|
|
checkedSetLongField(metadata.controlLong->upper_ctrl_limit, control.limitHigh);
|
|
}
|
|
}
|
|
|
|
// Control double
|
|
if (requestedOptions & DBR_CTRL_DOUBLE) {
|
|
getMetadataBuffer(pValueBuffer, const struct dbr_ctrlDouble, metadata.controlDouble, dbr_ctrlDouble_size);
|
|
if (actualOptions & DBR_CTRL_DOUBLE && value["control"]) {
|
|
checkedSetDoubleField(metadata.controlDouble->lower_ctrl_limit, control.limitLow);
|
|
checkedSetDoubleField(metadata.controlDouble->upper_ctrl_limit, control.limitHigh);
|
|
}
|
|
}
|
|
|
|
// Alarm long
|
|
if (requestedOptions & DBR_AL_LONG) {
|
|
getMetadataBuffer(pValueBuffer, const struct dbr_alLong, metadata.alarmLong, dbr_alLong_size);
|
|
if (actualOptions & DBR_AL_LONG && value["valueAlarm"]) {
|
|
checkedSetLongField(metadata.alarmLong->lower_alarm_limit, valueAlarm.lowAlarmLimit);
|
|
checkedSetLongField(metadata.alarmLong->lower_warning_limit, valueAlarm.lowWarningLimit);
|
|
checkedSetLongField(metadata.alarmLong->upper_warning_limit, valueAlarm.highWarningLimit);
|
|
checkedSetLongField(metadata.alarmLong->upper_alarm_limit, valueAlarm.highAlarmLimit);
|
|
}
|
|
}
|
|
|
|
// Alarm double
|
|
if (requestedOptions & DBR_AL_DOUBLE) {
|
|
getMetadataBuffer(pValueBuffer, const struct dbr_alDouble, metadata.alarmDouble, dbr_alDouble_size);
|
|
if (actualOptions & DBR_AL_DOUBLE && value["valueAlarm"]) {
|
|
checkedSetDoubleField(metadata.alarmDouble->lower_alarm_limit, valueAlarm.lowAlarmLimit);
|
|
checkedSetDoubleField(metadata.alarmDouble->lower_warning_limit, valueAlarm.lowWarningLimit);
|
|
checkedSetDoubleField(metadata.alarmDouble->upper_warning_limit, valueAlarm.highWarningLimit);
|
|
checkedSetDoubleField(metadata.alarmDouble->upper_alarm_limit, valueAlarm.highAlarmLimit);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // pvxs
|
|
} // ioc
|