epics-modules / autosave

APS BCDA synApps module: autosave
https://epics-modules.github.io/autosave/
Other
8 stars 31 forks source link

Problem restoring long strings of length 0 in base 3.15.6 and 7.03 #35

Closed MarkRivers closed 4 years ago

MarkRivers commented 4 years ago

There is a problem restoring long strings of length 0 in EPICS base 7. It was restoring the string "0" rather than a 0 length string.

@MarkRivers said:

The message in SR_get_array is printed here:

status = dbGet(paddr, request_field_type, pArray, NULL, pnum_elements, NULL);
if (save_restoreDebug >= 10) {
errlogPrintf("dbrestore:SR_get_array: '%s' currently has %ld  elements\n", PVname, *pnum_elements);
}

So it seems clear what the problem is. In 3.15.5 dbGet() is returning 120 (the maximum string size) while 7.0.1 is returning the actual string size including the terminating 0 byte.

This seems like a major difference.

@anjohnson replied:

First, note that all the PV names being printed by your debug messages end with $ so they are being requested as an array of chars, not as a DBF_STRING (which is correct, the latter would truncate the value at 40 characters). The IOC code that handles long strings has been improved several times since that functionality was originally added.

When you ask dbGet() for an array, it tells you how many elements it wrote to your buffer in *pnum_elements (on input this holds the maximum number the buffer can accept). In Base-3.15.5 and earlier for long-strings that are accessed as arrays we just returned the length of the whole buffer, but I added this optimization towards the end of dbGet() in April so it will be in Base-3.15.6 and new 7.0 releases:

if(!status && dbrType==DBF_CHAR && nRequest &&
paddr->pfldDes && paddr->pfldDes->field_type==DBF_STRING)
{
/* long string ensure nil and truncate to actual length */
long nReq = *nRequest;
pbuf[nReq-1] = '\0';
*nRequest = strlen(pbuf)+1;
}

The write_it() code you quoted earlier is seeing the single element reported for an empty string as being a scalar integer with value 0, instead of first checking the long string flag and handling that appropriately:

errno = 0;
if (pchannel->curr_elements <= 1) {
/* treat as scalar */
if (pchannel->enum_val >= 0) {
n = fprintf(out_fd, "%d\n",pchannel->enum_val);
} else {
n = fprintf(out_fd, "%-s\n", pchannel->value);
}
} else if (is_long_string) {
/* write first BUF-SIZE-1 characters of long string, so dbrestore doesn't choke. */

I think it just needs to do the if (is_long_string) check first, so this might be a relatively simple fix.

MarkRivers commented 4 years ago

My mistake, this was fixed in #21.