/*************************************************************************\
* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne
*     National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
*     Operator of Los Alamos National Laboratory.
* SPDX-License-Identifier: EPICS
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/

/* database access test subroutines */

#include <stddef.h>
#include <string.h>
#include <ctype.h>

#include "cvtFast.h"
#include "dbDefs.h"
#include "ellLib.h"
#include "epicsMutex.h"
#include "epicsStdio.h"
#include "epicsStdlib.h"
#include "epicsString.h"
#include "errlog.h"

#include "callback.h"
#include "dbAccessDefs.h"
#include "dbAddr.h"
#include "dbBase.h"
#include "dbCommon.h"
#include "dbEvent.h"
#include "db_field_log.h"
#include "dbFldTypes.h"
#include "dbLock.h"
#include "dbStaticLib.h"
#include "dbTest.h"
#include "devSup.h"
#include "drvSup.h"
#include "link.h"
#include "recGbl.h"
#include "recSup.h"
#include "special.h"
#include "dbConvertJSON.h"

#define MAXLINE 80
#define MAXMESS 128
struct msgBuff {    /* line output structure */
    char            out_buff[MAXLINE + 1];
    char           *pNext;
    char           *pLast;
    char           *pNexTab;
    char            message[MAXMESS];
};
typedef struct msgBuff TAB_BUFFER;

#ifndef MIN
#   define MIN(x,y)  (((x) < (y)) ? (x) : (y))
#endif
#ifndef MAX
#   define MAX(x,y)  (((x) > (y)) ? (x) : (y))
#endif

/* Local Routines */
static long nameToAddr(const char *pname, DBADDR *paddr);
static void printDbAddr(DBADDR *paddr);
static void printBuffer(long status, short dbr_type, void *pbuffer,
    long reqOptions, long retOptions, long no_elements,
    TAB_BUFFER *pMsgBuff, int tab_size);
static int dbpr_report(const char *pname, DBADDR *paddr, int interest_level,
    TAB_BUFFER *pMsgBuff, int tab_size);
static void dbpr_msgOut(TAB_BUFFER *pMsgBuff,int tab_size);
static void dbpr_init_msg(TAB_BUFFER *pMsgBuff,int tab_size);
static void dbpr_insert_msg(TAB_BUFFER *pMsgBuff,size_t len,int tab_size);
static void dbpr_msg_flush(TAB_BUFFER *pMsgBuff,int tab_size);

static char *dbf[DBF_NTYPES] = {
    "STRING","CHAR","UCHAR","SHORT","USHORT","LONG","ULONG",
    "INT64","UINT64","FLOAT","DOUBLE","ENUM","MENU","DEVICE",
    "INLINK","OUTLINK","FWDLINK","NOACCESS"
};

static char *dbr[DBR_ENUM+2] = {
    "STRING","CHAR","UCHAR","SHORT","USHORT","LONG","ULONG",
    "INT64","UINT64","FLOAT","DOUBLE","ENUM","NOACCESS"
};

long dba(const char*pname)
{
    DBADDR addr;

    if (!pname || !*pname) {
        printf("Usage: dba \"pv name\"\n");
        return 1;
    }

    if (nameToAddr(pname, &addr))
        return -1;

    printDbAddr(&addr);
    return 0;
}

/* split a space separated list of field names and return the number of
   fields with each field an element of papfields. These elements
   point to within the fieldnames variable which is modified
   by the function. memory is allocated for *ppapfields and needs
   to be freed later by the calling routine */
static int splitFieldsList(char *fieldnames, char ***ppapfields)
{
    char *pnext = fieldnames;
    int nfields = 0, maxfields = 1;
    char* saveptr = NULL;
    /* this may overcount real fields e.g. " VAL " hence maxfields */
    while (*pnext && (pnext = strchr(pnext, ' '))) {
        maxfields++;
        while (*pnext == ' ') pnext++;
    }
    *ppapfields = dbCalloc(maxfields, sizeof(char *));
    pnext = epicsStrtok_r(fieldnames, " ", &saveptr);
    while(pnext != NULL) {
        (*ppapfields)[nfields++] = pnext;
        pnext = epicsStrtok_r(NULL, " ", &saveptr);
    }
    return nfields;
}

static void printFieldsList(DBENTRY *pdbentry, char** papfields, int nfields)
{
    int ifield;
    for (ifield = 0; ifield < nfields; ifield++) {
        char *pvalue;
        long status = dbFindField(pdbentry, papfields[ifield]);
        if (status) {
            if (!strcmp(papfields[ifield], "recordType")) {
                pvalue = dbGetRecordTypeName(pdbentry);
            }
            else {
                printf(", ");
                continue;
            }
        }
        else {
            pvalue = dbGetString(pdbentry);
        }
        printf(", \"%s\"", (pvalue ? pvalue : ""));
    }
    printf("\n");
}    

long dbl(const char *precordTypename, const char *fields)
{
    DBENTRY dbentry;
    DBENTRY *pdbentry=&dbentry;
    long status;
    int nfields = 0;
    char *fieldnames = 0;
    char **papfields = 0;

    if (!pdbbase) {
        printf("No database loaded\n");
        return 0;
    }

    if (precordTypename &&
        ((*precordTypename == '\0') || !strcmp(precordTypename,"*")))
        precordTypename = NULL;
    if (fields && (*fields == '\0'))
        fields = NULL;
    if (fields) {
        fieldnames = epicsStrDup(fields);
        nfields = splitFieldsList(fieldnames, &papfields);
    }
    dbInitEntry(pdbbase, pdbentry);
    if (!precordTypename)
        status = dbFirstRecordType(pdbentry);
    else
        status = dbFindRecordType(pdbentry,precordTypename);
    if (status) {
        printf("No record type\n");
    }

    while (!status) {
        status = dbFirstRecord(pdbentry);
        while (!status) {
            printf("%s", dbGetRecordName(pdbentry));
            printFieldsList(pdbentry, papfields, nfields);
            status = dbNextRecord(pdbentry);
        }
        if (precordTypename)
            break;

        status = dbNextRecordType(pdbentry);
    }
    if (nfields > 0) {
        free(papfields);
        free(fieldnames);
    }
    dbFinishEntry(pdbentry);
    return 0;
}

long dbnr(int verbose)
{
    DBENTRY dbentry;
    DBENTRY *pdbentry=&dbentry;
    long status;
    int nrecords;
    int naliases;
    int trecords = 0;
    int taliases = 0;

    if (!pdbbase) {
        printf("No database loaded\n");
        return 0;
    }

    dbInitEntry(pdbbase, pdbentry);
    status = dbFirstRecordType(pdbentry);
    if (status) {
        printf("No record types loaded\n");
        return 0;
    }

    printf("Records  Aliases  Record Type\n");
    while (!status) {
        naliases = dbGetNAliases(pdbentry);
        taliases += naliases;
        nrecords = dbGetNRecords(pdbentry) - naliases;
        trecords += nrecords;
        if (verbose || nrecords)
            printf(" %5d    %5d    %s\n",
                nrecords, naliases, dbGetRecordTypeName(pdbentry));
        status = dbNextRecordType(pdbentry);
    }

    dbFinishEntry(pdbentry);
    printf("Total %d records, %d aliases\n", trecords, taliases);
    return 0;
}

long dbla(const char *pmask)
{
    DBENTRY dbentry;
    DBENTRY *pdbentry = &dbentry;
    long status;

    if (!pdbbase) {
        printf("No database loaded\n");
        return 0;
    }

    dbInitEntry(pdbbase, pdbentry);
    status = dbFirstRecordType(pdbentry);
    while (!status) {
        for (status = dbFirstRecord(pdbentry); !status;
             status = dbNextRecord(pdbentry)) {
            char *palias;

            if (!dbIsAlias(pdbentry))
                continue;

            palias = dbGetRecordName(pdbentry);
            if (pmask && *pmask && !epicsStrGlobMatch(palias, pmask))
                continue;
            dbFindField(pdbentry, "NAME");
            printf("%s -> %s\n", palias, dbGetString(pdbentry));
        }
        status = dbNextRecordType(pdbentry);
    }

    dbFinishEntry(pdbentry);
    return 0;
}

long dbli(const char *pattern)
{
    DBENTRY dbentry;
    void* ptr;

    if (!pdbbase) {
        printf("No database loaded\n");
        return 0;
    }

    dbInitEntry(pdbbase, &dbentry);
    while (dbNextMatchingInfo(&dbentry, pattern) == 0)
    {
        printf("%s info(%s, \"%s\"", dbGetRecordName(&dbentry),
            dbGetInfoName(&dbentry), dbGetInfoString(&dbentry));
        if ((ptr = dbGetInfoPointer(&dbentry)) != NULL)
            printf(", %p", ptr);
        printf(")\n");
    }
    dbFinishEntry(&dbentry);
    return 0;
}

long dbglob(const char *pmask,const char *fields)
{
    DBENTRY dbentry;
    DBENTRY *pdbentry = &dbentry;
    long status;
    int nfields = 0;
    char *fieldnames = 0;
    char **papfields = 0;

    if (!pmask || !*pmask) {
        printf("Usage: dbglob \"pattern\" \"fields\"\n");
        return 1;
    }

    if (!pdbbase) {
        printf("No database loaded\n");
        return 0;
    }
    if (fields && (*fields == '\0'))
        fields = NULL;
    if (fields) {
        fieldnames = epicsStrDup(fields);
        nfields = splitFieldsList(fieldnames, &papfields);
    }
    dbInitEntry(pdbbase, pdbentry);
    status = dbFirstRecordType(pdbentry);
    while (!status) {
        status = dbFirstRecord(pdbentry);
        while (!status) {
            char *pname = dbGetRecordName(pdbentry);
            if (epicsStrGlobMatch(pname, pmask)) {
                printf("%s", pname);
                printFieldsList(pdbentry, papfields, nfields);
            }
            status = dbNextRecord(pdbentry);
        }
        status = dbNextRecordType(pdbentry);
    }
    if (nfields > 0) {
        free(papfields);
        free(fieldnames);
    }
    dbFinishEntry(pdbentry);
    return 0;
}

long dbgrep(const char *pname, const char *fields) {
    return dbglob(pname, fields);
}

long dbgf(const char *pname)
{
    /* declare buffer long just to ensure correct alignment */
    long buffer[100];
    long *pbuffer=&buffer[0];
    DBADDR addr;
    long options = 0;
    long no_elements;
    static TAB_BUFFER msg_Buff;

    if (!pname || !*pname) {
        printf("Usage: dbgf \"pv name\"\n");
        return 1;
    }

    if (nameToAddr(pname, &addr))
        return -1;

    if (addr.precord->lset == NULL) {
        printf("dbgf only works after iocInit\n");
        return -1;
    }

    no_elements = MIN(addr.no_elements, sizeof(buffer)/addr.field_size);
    if (addr.dbr_field_type == DBR_ENUM) {
        long status = dbGetField(&addr, DBR_STRING, pbuffer,
            &options, &no_elements, NULL);

        printBuffer(status, DBR_STRING, pbuffer, 0L, 0L,
            no_elements, &msg_Buff, 10);
    }
    else {
        long status = dbGetField(&addr, addr.dbr_field_type, pbuffer,
            &options, &no_elements, NULL);

        printBuffer(status, addr.dbr_field_type, pbuffer, 0L, 0L,
            no_elements, &msg_Buff, 10);
    }

    msg_Buff.message[0] = '\0';
    dbpr_msgOut(&msg_Buff, 10);
    return 0;
}

long dbpf(const char *pname,const char *pvalue)
{
    DBADDR addr;
    long status;
    short dbrType = DBR_STRING;
    long n = 1;
    char *array = NULL;

    if (!pname || !*pname || !pvalue) {
        printf("Usage: dbpf \"pv name\", \"value\"\n");
        return 1;
    }

    if (nameToAddr(pname, &addr))
        return -1;

    if (addr.precord->lset == NULL) {
        printf("dbpf only works after iocInit\n");
        return -1;
    }

    if (addr.no_elements > 1) {
        dbrType = addr.dbr_field_type;
        if (addr.dbr_field_type == DBR_CHAR || addr.dbr_field_type == DBR_UCHAR) {
            n = (long)strlen(pvalue) + 1;
        } else {
            n = addr.no_elements;
            array = calloc(n, dbValueSize(dbrType));
            if (!array) {
                printf("Out of memory\n");
                return -1;
            }
            status = dbPutConvertJSON(pvalue, dbrType, array, &n);
            if (status)
                return status;
            pvalue = array;
        }
    }
    status = dbPutField(&addr, dbrType, pvalue, n);
    free(array);
    dbgf(pname);
    return status;
}

long dbpr(const char *pname,int interest_level)
{
    static TAB_BUFFER msg_Buff;
    TAB_BUFFER *pMsgBuff = &msg_Buff;
    DBADDR addr;
    char *pmsg;
    int tab_size = 20;

    if (!pname || !*pname) {
        printf("Usage: dbpr \"pv name\", level\n");
        return 1;
    }

    if (nameToAddr(pname, &addr))
        return -1;

    pmsg = pMsgBuff->message;

    if (dbpr_report(pname, &addr, interest_level, pMsgBuff, tab_size))
        return 1;

    pmsg[0] = '\0';
    dbpr_msgOut(pMsgBuff, tab_size);
    return 0;
}

long dbtr(const char *pname)
{
    DBADDR addr;
    long status;
    struct dbCommon *precord;

    if (!pname || !*pname) {
        printf("Usage: dbtr \"pv name\"\n");
        return 1;
    }

    if (nameToAddr(pname, &addr))
        return -1;

    if (addr.precord->lset == NULL) {
        printf("dbtr only works after iocInit\n");
        return -1;
    }

    precord = (struct dbCommon*)addr.precord;
    if (precord->pact) {
        printf("record active\n");
        return 1;
    }

    dbScanLock(precord);
    status = dbProcess(precord);
    dbScanUnlock(precord);

    if (status)
        recGblRecordError(status, precord, "dbtr(dbProcess)");

    dbpr(pname, 3);
    return 0;
}

long dbtgf(const char *pname)
{
    /* declare buffer long just to ensure correct alignment */
    long       buffer[400];
    long       *pbuffer = &buffer[0];
    DBADDR     addr;
    long       status;
    long       req_options, ret_options, no_elements;
    short      dbr_type;
    static TAB_BUFFER msg_Buff;
    TAB_BUFFER *pMsgBuff = &msg_Buff;
    char       *pmsg = pMsgBuff->message;
    int        tab_size = 10;

    if (pname==0 || *pname==0) {
        printf("Usage: dbtgf \"pv name\"\n");
        return 1;
    }

    if (nameToAddr(pname, &addr))
        return -1;

    if (addr.precord->lset == NULL) {
        printf("dbtgf only works after iocInit\n");
        return -1;
    }

    /* try all options first */
    req_options = 0xffffffff;
    ret_options = req_options;
    no_elements = 0;
    status = dbGetField(&addr, addr.dbr_field_type, pbuffer,
        &ret_options, &no_elements, NULL);
    printBuffer(status, addr.dbr_field_type, pbuffer,
        req_options, ret_options, no_elements, pMsgBuff, tab_size);

    /* Now try all request types */
    ret_options=0;

    dbr_type = DBR_STRING;
    no_elements = MIN(addr.no_elements,(sizeof(buffer)/MAX_STRING_SIZE));
    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL);
    printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size);

    dbr_type = DBR_CHAR;
    no_elements = MIN(addr.no_elements,(sizeof(buffer)/(sizeof(epicsInt8))));
    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL);
    printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size);

    dbr_type = DBR_UCHAR;
    no_elements = MIN(addr.no_elements,(sizeof(buffer)/(sizeof(epicsUInt8))));
    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL);
    printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size);

    dbr_type = DBR_SHORT;
    no_elements = MIN(addr.no_elements,(sizeof(buffer)/(sizeof(epicsInt16))));
    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL);
    printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size);

    dbr_type = DBR_USHORT;
    no_elements = MIN(addr.no_elements,(sizeof(buffer)/(sizeof(epicsUInt16))));
    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL);
    printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size);

    dbr_type = DBR_LONG;
    no_elements = MIN(addr.no_elements,(sizeof(buffer)/(sizeof(epicsInt32))));
    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL);
    printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size);

    dbr_type = DBR_ULONG;
    no_elements = MIN(addr.no_elements,(sizeof(buffer)/(sizeof(epicsUInt32))));
    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL);
    printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size);

    dbr_type = DBR_INT64;
    no_elements = MIN(addr.no_elements,(sizeof(buffer)/(sizeof(epicsInt64))));
    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL);
    printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size);

    dbr_type = DBR_UINT64;
    no_elements = MIN(addr.no_elements,(sizeof(buffer)/(sizeof(epicsUInt64))));
    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL);
    printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size);

    dbr_type = DBR_FLOAT;
    no_elements = MIN(addr.no_elements,(sizeof(buffer)/(sizeof(epicsFloat32))));
    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL);
    printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size);

    dbr_type = DBR_DOUBLE;
    no_elements = MIN(addr.no_elements,(sizeof(buffer)/(sizeof(epicsFloat64))));
    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL);
    printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size);

    dbr_type = DBR_ENUM;
    no_elements = MIN(addr.no_elements,(sizeof(buffer)/(sizeof(epicsEnum16))));
    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL);
    printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size);

    pmsg[0] = '\0';
    dbpr_msgOut(pMsgBuff, tab_size);
    return(0);
}

long dbtpf(const char *pname, const char *pvalue)
{
    /* declare buffer long just to ensure correct alignment */
    long buffer[100];
    long *pbuffer = buffer;
    DBADDR addr;
    int put_type;
    static TAB_BUFFER msg_Buff;
    TAB_BUFFER *pMsgBuff = &msg_Buff;
    char *pmsg = pMsgBuff->message;
    int tab_size = 10;

    if (!pname || !*pname || !pvalue) {
        printf("Usage: dbtpf \"pv name\", \"value\"\n");
        return 1;
    }
    if (nameToAddr(pname, &addr))
        return -1;

    if (addr.precord->lset == NULL) {
        printf("dbtpf only works after iocInit\n");
        return -1;
    }

    for (put_type = DBR_STRING; put_type <= DBF_ENUM; put_type++) {
        union {
            epicsInt8 i8;
            epicsUInt8 u8;
            epicsInt16 i16;
            epicsUInt16 u16;
            epicsInt32 i32;
            epicsUInt32 u32;
            epicsInt64 i64;
            epicsUInt64 u64;
            epicsFloat32 f32;
            epicsFloat64 f64;
            epicsEnum16 e16;
        } val;
        const void *pval = &val;
        int valid = 1;

        switch (put_type) {
        case DBR_STRING:
            pval = pvalue;
            break;
        case DBR_CHAR:
            valid = !epicsParseInt8(pvalue, &val.i8, 10, NULL);
            break;
        case DBR_UCHAR:
            valid = !epicsParseUInt8(pvalue, &val.u8, 10, NULL);
            break;
        case DBR_SHORT:
            valid = !epicsParseInt16(pvalue, &val.i16, 10, NULL);
            break;
        case DBR_USHORT:
            valid = !epicsParseUInt16(pvalue, &val.u16, 10, NULL);
            break;
        case DBR_LONG:
            valid = !epicsParseInt32(pvalue, &val.i32, 10, NULL);
            break;
        case DBR_ULONG:
            valid = !epicsParseUInt32(pvalue, &val.u32, 10, NULL);
            break;
        case DBR_INT64:
            valid = !epicsParseInt64(pvalue, &val.i64, 10, NULL);
            break;
        case DBR_UINT64:
            valid = !epicsParseUInt64(pvalue, &val.u64, 10, NULL);
            break;
        case DBR_FLOAT:
            valid = !epicsParseFloat32(pvalue, &val.f32, NULL);
            break;
        case DBR_DOUBLE:
            valid = !epicsParseFloat64(pvalue, &val.f64, NULL);
            break;
        case DBR_ENUM:
            valid = !epicsParseUInt16(pvalue, &val.e16, 10, NULL);
            break;
        }
        if (valid) {
            long status = dbPutField(&addr, put_type, pval, 1);

            if (status) {
                printf("Put as DBR_%-6s Failed.\n", dbr[put_type]);
            }
            else {
                long options = 0;
                long no_elements = MIN(addr.no_elements,
                    ((sizeof(buffer))/addr.field_size));

                printf("Put as DBR_%-6s Ok, result as ", dbr[put_type]);
                status = dbGetField(&addr, addr.dbr_field_type, pbuffer,
                    &options, &no_elements, NULL);
                printBuffer(status, addr.dbr_field_type, pbuffer, 0L, 0L,
                    no_elements, pMsgBuff, tab_size);
            }
        }
        else {
            printf("Cvt to DBR_%s failed.\n", dbr[put_type]);
        }
    }

    pmsg[0] = '\0';
    dbpr_msgOut(pMsgBuff, tab_size);
    return 0;
}

long dbior(const char *pdrvName,int interest_level)
{
    drvSup *pdrvSup;
    drvet *pdrvet;
    dbRecordType *pdbRecordType;

    if (!pdbbase) {
        printf("No database loaded\n");
        return 0;
    }

    if (pdrvName && ((*pdrvName == '\0') || !strcmp(pdrvName,"*")))
        pdrvName = NULL;

    for (pdrvSup = (drvSup *)ellFirst(&pdbbase->drvList);
         pdrvSup;
         pdrvSup = (drvSup *)ellNext(&pdrvSup->node)) {
        const char *pname = pdrvSup->name;

        if (pdrvName!=NULL && *pdrvName!='\0' &&
            (strcmp(pdrvName,pname)!=0))
            continue;

        pdrvet = pdrvSup->pdrvet ;
        if (pdrvet == NULL) {
            printf("No driver entry table is present for %s\n", pname);
            continue;
        }

        if (pdrvet->report == NULL)
            printf("Driver: %s No report available\n", pname);
        else {
            printf("Driver: %s\n", pname);
            pdrvet->report(interest_level);
        }
    }

    /* now check devSup reports */
    for (pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList);
         pdbRecordType;
         pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) {
        devSup *pdevSup;

        for (pdevSup = (devSup *)ellFirst(&pdbRecordType->devList);
             pdevSup;
             pdevSup = (devSup *)ellNext(&pdevSup->node)) {
            dset *pdset = pdevSup->pdset;
            const char *pname  = pdevSup->name;

            if (!pdset || !pname)
                continue;

            if (pdrvName != NULL && *pdrvName != '\0' &&
                (strcmp(pdrvName, pname) != 0))
                continue;

            if (pdset->report != NULL) {
                printf("Device Support: %s\n", pname);
                pdset->report(interest_level);
            }
        }
    }
    return 0;
}

int dbhcr(void)
{
    if (!pdbbase) {
        printf("No database loaded\n");
        return 0;
    }

    dbReportDeviceConfig(pdbbase, stdout);
    return 0;
}

static long nameToAddr(const char *pname, DBADDR *paddr)
{
    long status = dbNameToAddr(pname, paddr);

    if (status) {
        printf("PV '%s' not found\n", pname);
    }
    return status;
}

static void printDbAddr(DBADDR *paddr)
{
    dbFldDes *pdbFldDes = paddr->pfldDes;
    short field_type = paddr->field_type;
    short dbr_field_type = paddr->dbr_field_type;

    printf("Record Address: %p", paddr->precord);
    printf(" Field Address: %p", paddr->pfield);
    printf(" Field Description: %p\n", pdbFldDes);
    printf("   No Elements: %ld\n", paddr->no_elements);
    printf("   Record Type: %s\n", pdbFldDes->pdbRecordType->name);
    printf("    Field Type: %d = DBF_%s\n", field_type,
        (field_type < 0 || field_type > DBR_NOACCESS) ? "????" :
         dbf[field_type]);
    printf("    Field Size: %d\n", paddr->field_size);
    printf("       Special: %d\n", paddr->special);

    if (dbr_field_type == DBR_NOACCESS)
        dbr_field_type = DBR_ENUM + 1;
    printf("DBR Field Type: %d = DBR_%s\n", paddr->dbr_field_type,
        (dbr_field_type < 0 || dbr_field_type > (DBR_ENUM+1)) ? "????" :
        dbr[dbr_field_type]);
}


static void printBuffer(
    long status, short dbr_type, void *pbuffer, long reqOptions,
    long retOptions, long no_elements, TAB_BUFFER *pMsgBuff, int tab_size)
{
    char *pmsg = pMsgBuff->message;
    int i;

    if (reqOptions & DBR_STATUS) {
        if (retOptions & DBR_STATUS) {
            struct dbr_status *pdbr_status = pbuffer;

            printf("status = %u, severity = %u\n",
                pdbr_status->status,
                pdbr_status->severity);
        }
        else {
            printf("status and severity not returned\n");
        }
        pbuffer = (char *)pbuffer + dbr_status_size;
    }

    if (reqOptions & DBR_UNITS) {
        if (retOptions & DBR_UNITS) {
            struct dbr_units *pdbr_units = pbuffer;

            printf("units = \"%s\"\n",
                pdbr_units->units);
        }
        else {
            printf("units not returned\n");
        }
        pbuffer = (char *)pbuffer + dbr_units_size;
    }

    if (reqOptions & DBR_PRECISION) {
        if (retOptions & DBR_PRECISION){
            struct dbr_precision *pdbr_precision = pbuffer;

            printf("precision = %ld\n",
                pdbr_precision->precision.dp);
        }
        else {
            printf("precision not returned\n");
        }
        pbuffer = (char *)pbuffer + dbr_precision_size;
    }

    if (reqOptions & DBR_TIME) {
        if (retOptions & DBR_TIME) {
            struct dbr_time *pdbr_time = pbuffer;
            char time_buf[40];
            epicsTimeToStrftime(time_buf, 40, "%Y-%m-%d %H:%M:%S.%09f",
                &pdbr_time->time);
            printf("time = %s\n", time_buf);
        }
        else {
            printf("time not returned\n");
        }
        pbuffer = (char *)pbuffer + dbr_time_size;
    }

    if (reqOptions & DBR_ENUM_STRS) {
        if (retOptions & DBR_ENUM_STRS) {
            struct dbr_enumStrs *pdbr_enumStrs = pbuffer;

            printf("no_strs = %u:\n",
                pdbr_enumStrs->no_str);
            for (i = 0; i < pdbr_enumStrs->no_str; i++)
                printf("\t\"%s\"\n", pdbr_enumStrs->strs[i]);
        }
        else {
            printf("enum strings not returned\n");
        }
        pbuffer = (char *)pbuffer + dbr_enumStrs_size;
    }

    if (reqOptions & DBR_GR_LONG) {
        if (retOptions & DBR_GR_LONG) {
            struct dbr_grLong *pdbr_grLong = pbuffer;

            printf("grLong: %d .. %d\n",
                pdbr_grLong->lower_disp_limit,
                pdbr_grLong->upper_disp_limit);
        }
        else {
            printf("DBRgrLong not returned\n");
        }
        pbuffer = (char *)pbuffer + dbr_grLong_size;
    }

    if (reqOptions & DBR_GR_DOUBLE) {
        if (retOptions & DBR_GR_DOUBLE) {
            struct dbr_grDouble *pdbr_grDouble = pbuffer;

            printf("grDouble: %g .. %g\n",
                pdbr_grDouble->lower_disp_limit,
                pdbr_grDouble->upper_disp_limit);
        }
        else {
            printf("DBRgrDouble not returned\n");
        }
        pbuffer = (char *)pbuffer + dbr_grDouble_size;
    }

    if (reqOptions & DBR_CTRL_LONG) {
        if (retOptions & DBR_CTRL_LONG){
            struct dbr_ctrlLong *pdbr_ctrlLong = pbuffer;

            printf("ctrlLong: %d .. %d\n",
                pdbr_ctrlLong->lower_ctrl_limit,
                pdbr_ctrlLong->upper_ctrl_limit);
        }
        else {
            printf("DBRctrlLong not returned\n");
        }
        pbuffer = (char *)pbuffer + dbr_ctrlLong_size;
    }

    if (reqOptions & DBR_CTRL_DOUBLE) {
        if (retOptions & DBR_CTRL_DOUBLE) {
            struct dbr_ctrlDouble *pdbr_ctrlDouble = pbuffer;

            printf("ctrlDouble: %g .. %g\n",
                pdbr_ctrlDouble->lower_ctrl_limit,
                pdbr_ctrlDouble->upper_ctrl_limit);
        }
        else {
            printf("DBRctrlDouble not returned\n");
        }
        pbuffer = (char *)pbuffer + dbr_ctrlDouble_size;
    }

    if (reqOptions & DBR_AL_LONG) {
        if (retOptions & DBR_AL_LONG) {
            struct dbr_alLong *pdbr_alLong = pbuffer;

            printf("alLong: %d < %d .. %d < %d\n",
                pdbr_alLong->lower_alarm_limit,
                pdbr_alLong->lower_warning_limit,
                pdbr_alLong->upper_warning_limit,
                pdbr_alLong->upper_alarm_limit);
        }
        else {
            printf("DBRalLong not returned\n");
        }
        pbuffer = (char *)pbuffer + dbr_alLong_size;
    }

    if (reqOptions & DBR_AL_DOUBLE) {
        if (retOptions & DBR_AL_DOUBLE) {
            struct dbr_alDouble *pdbr_alDouble = pbuffer;

            printf("alDouble: %g < %g .. %g < %g\n",
                pdbr_alDouble->lower_alarm_limit,
                pdbr_alDouble->lower_warning_limit,
                pdbr_alDouble->upper_warning_limit,
                pdbr_alDouble->upper_alarm_limit);
        }
        else {
            printf("DBRalDouble not returned\n");
        }
        pbuffer = (char *)pbuffer + dbr_alDouble_size;
    }

    /* Now print values */
    if (no_elements == 1)
        sprintf(pmsg, "DBF_%s: ", dbr[dbr_type]);
    else {
        sprintf(pmsg, "DBF_%s[%ld]: ", dbr[dbr_type], no_elements);
        if (no_elements == 0)
            strcat(pmsg, "(empty)");
    }
    dbpr_msgOut(pMsgBuff, tab_size);

    if (status != 0) {
        strcpy(pmsg, "failed.");
        dbpr_msgOut(pMsgBuff, tab_size);
    }
    else {
        switch (dbr_type) {
        case DBR_STRING:
            for(i=0; i<no_elements; i++) {
                size_t len = strlen(pbuffer);

                strcpy(pmsg, "\"");
                epicsStrnEscapedFromRaw(pmsg+1, MAXLINE - 3,
                    (char *)pbuffer, len);
                strcat(pmsg, "\"");
                dbpr_msgOut(pMsgBuff, tab_size);
                pbuffer = (char *)pbuffer + MAX_STRING_SIZE;
            }
            break;

        case DBR_CHAR:
            if (no_elements == 1) {
                epicsInt32 val = *(epicsInt8 *) pbuffer;

                if (isprint(val))
                    sprintf(pmsg, "%d = 0x%x = '%c'", val, val & 0xff, val);
                else
                    sprintf(pmsg, "%d = 0x%x", val, val & 0xff);
                dbpr_msgOut(pMsgBuff, tab_size);
            } else {
                size_t len = epicsStrnLen(pbuffer, no_elements);

                i = 0;
                while (len > 0) {
                    int chunk = (len > MAXLINE - 5) ? MAXLINE - 5 : len;
                    strcpy(pmsg, "\"");
                    while (epicsStrnEscapedFromRawSize((char *)pbuffer + i, chunk) >= MAXLINE - 5)
                        chunk--;
                    epicsStrnEscapedFromRaw(pmsg+1, MAXLINE - 5,
                        (char *)pbuffer + i, chunk);
                    strcat(pmsg, "\"");
                    len -= chunk; i += chunk;
                    if (len > 0)
                        strcat(pmsg, " +");
                    dbpr_msgOut(pMsgBuff, tab_size);
                }
            }
            break;

        case DBR_UCHAR:
            for (i = 0; i < no_elements; i++) {
                epicsUInt32 val = *(epicsUInt8 *) pbuffer;

                sprintf(pmsg, "%u = 0x%x", val, val);
                dbpr_msgOut(pMsgBuff, tab_size);
                pbuffer = (char *)pbuffer + sizeof(epicsUInt8);
            }
            break;

        case DBR_SHORT:
            for (i = 0; i < no_elements; i++) {
                epicsInt16 val = *(epicsInt16 *) pbuffer;

                sprintf(pmsg, "%hd = 0x%hx", val, val);
                dbpr_msgOut(pMsgBuff, tab_size);
                pbuffer = (char *)pbuffer + sizeof(epicsInt16);
            }
            break;

        case DBR_USHORT:
            for (i = 0; i < no_elements; i++) {
                epicsUInt16 val = *(epicsUInt16 *) pbuffer;

                sprintf(pmsg, "%hu = 0x%hx", val, val);
                dbpr_msgOut(pMsgBuff, tab_size);
                pbuffer = (char *)pbuffer + sizeof(epicsUInt16);
            }
            break;

        case DBR_LONG:
            for (i = 0; i < no_elements; i++) {
                epicsInt32 val = *(epicsInt32 *) pbuffer;

                sprintf(pmsg, "%d = 0x%x", val, val);
                dbpr_msgOut(pMsgBuff, tab_size);
                pbuffer = (char *)pbuffer + sizeof(epicsInt32);
            }
            break;

        case DBR_ULONG:
            for (i = 0; i < no_elements; i++) {
                epicsUInt32 val = *(epicsUInt32 *) pbuffer;

                sprintf(pmsg, "%u = 0x%x", val, val);
                dbpr_msgOut(pMsgBuff, tab_size);
                pbuffer = (char *)pbuffer + sizeof(epicsUInt32);
            }
            break;

        case DBR_INT64:
            for (i = 0; i < no_elements; i++) {
                epicsInt64 val = *(epicsInt64 *) pbuffer;

                pmsg += cvtInt64ToString(val, pmsg);
                strcpy(pmsg, " = ");
                pmsg += 3;
                cvtInt64ToHexString(val, pmsg);
                dbpr_msgOut(pMsgBuff, tab_size);
                pmsg = pMsgBuff->message;
                pbuffer = (char *)pbuffer + sizeof(epicsInt64);
            }
            break;

        case DBR_UINT64:
            for (i = 0; i < no_elements; i++) {
                epicsUInt64 val = *(epicsUInt64 *) pbuffer;

                pmsg += cvtUInt64ToString(val, pmsg);
                strcpy(pmsg, " = ");
                pmsg += 3;
                cvtUInt64ToHexString(val, pmsg);
                dbpr_msgOut(pMsgBuff, tab_size);
                pmsg = pMsgBuff->message;
                pbuffer = (char *)pbuffer + sizeof(epicsUInt64);
            }
            break;

        case DBR_FLOAT:
            for (i = 0; i < no_elements; i++) {
                sprintf(pmsg, "%.6g", *((epicsFloat32 *) pbuffer));
                dbpr_msgOut(pMsgBuff, tab_size);
                pbuffer = (char *)pbuffer + sizeof(epicsFloat32);
            }
            break;

        case DBR_DOUBLE:
            for (i = 0; i < no_elements; i++) {
                sprintf(pmsg, "%.12g", *((epicsFloat64 *) pbuffer));
                dbpr_msgOut(pMsgBuff, tab_size);
                pbuffer = (char *)pbuffer + sizeof(epicsFloat64);
            }
            break;

        case DBR_ENUM:
            for (i = 0; i < no_elements; i++) {
                sprintf(pmsg, "%u", *((epicsEnum16 *) pbuffer));
                dbpr_msgOut(pMsgBuff, tab_size);
                pbuffer = (char *)pbuffer + sizeof(epicsEnum16);
            }
            break;

        default:
            sprintf(pmsg, "Bad DBR type %d", dbr_type);
            dbpr_msgOut(pMsgBuff, tab_size);
            break;
        }
    }

    dbpr_msg_flush(pMsgBuff, tab_size);
}

static int dbpr_report(
    const char *pname, DBADDR *paddr, int interest_level,
    TAB_BUFFER *pMsgBuff, int tab_size)
{
    char        *pmsg;
    dbFldDes    *pdbFldDes = paddr->pfldDes;
    dbRecordType *pdbRecordType = pdbFldDes->pdbRecordType;
    short       n2;
    void        *pfield;
    char        *pfield_name;
    char        *pfield_value;
    DBENTRY     dbentry;
    DBENTRY     *pdbentry = &dbentry;
    long        status;

    dbInitEntry(pdbbase,pdbentry);
    status = dbFindRecord(pdbentry,pname);
    if (status) {
        errMessage(status,pname);
        return -1;
    }

    pmsg = pMsgBuff->message;
    for (n2 = 0; n2 <= pdbRecordType->no_fields - 1; n2++) {
        pdbFldDes = pdbRecordType->papFldDes[pdbRecordType->sortFldInd[n2]];
        pfield_name = pdbFldDes->name;
        pfield = ((char *)paddr->precord) + pdbFldDes->offset;
        if (pdbFldDes->interest > interest_level )
            continue;
        switch (pdbFldDes->field_type) {
        case DBF_STRING:
        case DBF_USHORT:
        case DBF_ENUM:
        case DBF_FLOAT:
        case DBF_CHAR:
        case DBF_UCHAR:
        case DBF_SHORT:
        case DBF_LONG:
        case DBF_ULONG:
        case DBF_INT64:
        case DBF_UINT64:
        case DBF_DOUBLE:
        case DBF_MENU:
        case DBF_DEVICE:
            status = dbFindField(pdbentry,pfield_name);
            pfield_value = dbGetString(pdbentry);
            sprintf(pmsg, "%-4s: %s", pfield_name,
                (pfield_value ? pfield_value : "<nil>"));
            dbpr_msgOut(pMsgBuff, tab_size);
            break;

        case DBF_INLINK:
        case DBF_OUTLINK:
        case DBF_FWDLINK: {
                DBLINK  *plink = (DBLINK *)pfield;
                int     ind;
                const char *type = "LINK";

                status = dbFindField(pdbentry,pfield_name);
                if (!plink->text)
                    for (ind=0; ind<LINK_NTYPES; ind++) {
                        if (pamaplinkType[ind].value == plink->type) {
                            type = pamaplinkType[ind].strvalue;
                            break;
                        }
                    }
                epicsSnprintf(pmsg, MAXMESS, "%-4s: %s %s", pfield_name,
                    type, dbGetString(pdbentry));
                dbpr_msgOut(pMsgBuff, tab_size);
            }
            break;

        case DBF_NOACCESS:
            if (pfield == &paddr->precord->time) {
                /* Special for the TIME field, make it human-readable */
                char time_buf[40];
                epicsTimeToStrftime(time_buf, 40, "%Y-%m-%d %H:%M:%S.%09f",
                    &paddr->precord->time);
                sprintf(pmsg, "%-4s: %s", pfield_name, time_buf);
                dbpr_msgOut(pMsgBuff, tab_size);
            }
            else if (pdbFldDes->size == sizeof(void *) &&
                strchr(pdbFldDes->extra, '*')) {
                /* Special for pointers */
                sprintf(pmsg, "%-4s: PTR %p", pfield_name, *(void **)pfield);
                dbpr_msgOut(pMsgBuff, tab_size);
            }
            else if (pdbFldDes->size == sizeof(ELLLIST) &&
                !strncmp(pdbFldDes->extra, "ELLLIST", 7)) {
                /* Special for linked lists */
                ELLLIST *plist = (ELLLIST *)pfield;
                sprintf(pmsg, "%-4s: ELL %d [%p .. %p]", pfield_name,
                    ellCount(plist), ellFirst(plist), ellLast(plist));
                dbpr_msgOut(pMsgBuff, tab_size);
            }
            else { /* just print field as hex bytes */
                unsigned char *pchar = (unsigned char *)pfield;
                char   temp_buf[61];
                char *ptemp_buf = &temp_buf[0];
                short n = pdbFldDes->size;
                short i;
                unsigned int value;

                if (n > sizeof(temp_buf)/3) n = sizeof(temp_buf)/3;
                for (i=0; i<n; i++, ptemp_buf += 3, pchar++) {
                        value = (unsigned int)*pchar;
                        sprintf(ptemp_buf, "%02x ", value);
                }
                sprintf(pmsg, "%-4s: %s", pfield_name,temp_buf);
                dbpr_msgOut(pMsgBuff, tab_size);
            }
            break;

        default:
            sprintf(pmsg, "%s: dbpr: Unknown field_type", pfield_name);
            dbpr_msgOut(pMsgBuff, tab_size);
            break;
        }
    }
    pmsg[0] = '\0';
    dbpr_msgOut(pMsgBuff, tab_size);
    dbFinishEntry(pdbentry);
    return (0);
}

static void dbpr_msgOut(TAB_BUFFER *pMsgBuff,int tab_size)
{
    size_t     len;
    int        err = 0;
    char       *pmsg = pMsgBuff->message;
    static int last_tabsize;

    if (!((tab_size == 10) || (tab_size == 20))) {
        printf("tab_size not 10 or 20 - dbpr_msgOut()\n");
        return;
    }
    /* init if first time */
    if (!(pMsgBuff->pNext))
        dbpr_init_msg(pMsgBuff, tab_size);
    if (tab_size != last_tabsize)
        pMsgBuff->pNexTab = pMsgBuff->out_buff + tab_size;

    last_tabsize = tab_size;
    /* flush output if NULL string command */
    if (*pmsg == 0) {
        dbpr_msg_flush(pMsgBuff, tab_size);
        return;
    }
    /* truncate if too long */
    len = strlen(pmsg);
    if (len > MAXLINE) {
        err = 1;
        len = MAXLINE;
    }

    dbpr_insert_msg(pMsgBuff, len, tab_size);

    /* warn if msg gt 80 */
    if (err == 1) {
        len = strlen(pmsg);
        sprintf(pmsg, "dbpr_msgOut: ERROR - msg length=%d limit=%d ",
            (int)len, MAXLINE);
        dbpr_insert_msg(pMsgBuff, len, tab_size);
    }
}

static void dbpr_init_msg(TAB_BUFFER *pMsgBuff,int tab_size)
{
    pMsgBuff->pNext = pMsgBuff->out_buff;
    pMsgBuff->pLast = pMsgBuff->out_buff + MAXLINE;
    pMsgBuff->pNexTab = pMsgBuff->out_buff + tab_size;
}

static void dbpr_insert_msg(TAB_BUFFER *pMsgBuff,size_t len,int tab_size)
{
    size_t          current_len;
    size_t          n;
    size_t          tot_line;
    char           *pmsg = pMsgBuff->message;
    current_len = strlen(pMsgBuff->out_buff);
    tot_line = current_len + len;

    /* flush buffer if overflow would occur */
    if (tot_line > MAXLINE)
        dbpr_msg_flush(pMsgBuff, tab_size);

    /* append message to buffer */
    n = 0;
    while ((*pmsg) && (n < len)) {
        *pMsgBuff->pNext++ = *pmsg++;

        /* position to next tab stop */
        if (*(pMsgBuff->pNexTab - 1) != '\0')
            pMsgBuff->pNexTab = pMsgBuff->pNexTab + tab_size;
        n++;
    }

    /* fill spaces to next tab stop */
    while (*(pMsgBuff->pNexTab - 1) != ' '
            && pMsgBuff->pNext < pMsgBuff->pLast) {
        *pMsgBuff->pNext++ = ' ';
    }
}

static void dbpr_msg_flush(TAB_BUFFER *pMsgBuff,int tab_size)
{
    /* skip print if buffer empty */
    if (pMsgBuff->pNext != pMsgBuff->out_buff)
        printf("%s\n", pMsgBuff->out_buff);

    memset(pMsgBuff->out_buff,'\0', (int) MAXLINE + 1);
    pMsgBuff->pNext = pMsgBuff->out_buff;
    pMsgBuff->pNexTab = pMsgBuff->out_buff + tab_size;
}
