示例#1
0
PyObject *pv_getter_pvaccess(pvobject * self, void *closure)
{
	if (self->chanId)
		return PyString_FromFormat("%s", ca_read_access(self->chanId)
					   && ca_write_access(self->chanId) ?
					   "rw" : ca_read_access(self->chanId) ?
					   "ro" : ca_write_access(self->chanId)
					   ? "wo" : "None");
	else {
		PYCA_ERR("invalid chid");
	}
}
示例#2
0
static void accessRightsCallback(struct access_rights_handler_args arg)
{
    caLink *pca = (caLink *)ca_puser(arg.chid);
    struct link	*plink;
    struct pv_link *ppv_link;
    dbCommon *precord;

    assert(pca);
    if (ca_state(pca->chid) != cs_conn)
        return; /* connectionCallback will handle */
    epicsMutexMustLock(pca->lock);
    plink = pca->plink;
    if (!plink) goto done;
    pca->hasReadAccess = ca_read_access(arg.chid);
    pca->hasWriteAccess = ca_write_access(arg.chid);
    if (pca->hasReadAccess && pca->hasWriteAccess) goto done;
    ppv_link = &plink->value.pv_link;
    precord = ppv_link->precord;
    if (precord &&
        ((ppv_link->pvlMask & pvlOptCP) ||
         ((ppv_link->pvlMask & pvlOptCPP) && precord->scan == 0)))
        scanOnce(precord);
done:
    epicsMutexUnlock(pca->lock);
}
示例#3
0
static void printChidInfo(chid chid, char *message)
{
	printf("\n%s\n",message);
	printf("pv: %s  type(%d) nelements(%ld) host(%s)",
			ca_name(chid),ca_field_type(chid),ca_element_count(chid),
			ca_host_name(chid));
	printf(" read(%d) write(%d) state(%d)\n",
			ca_read_access(chid),ca_write_access(chid),ca_state(chid));
}
示例#4
0
static void exCB(struct exception_handler_args args)
{
#define MAX_EXCEPTIONS 25    
    static int nexceptions=0;
    static int ended=0;

    if(ended) return;
    if(nexceptions++ > MAX_EXCEPTIONS) {
        ended=1;
        fprintf(stderr,"exCB Exception: Channel Access Exception:\n"
          "Too many exceptions [%d]\n"
          "No more will be handled\n"
          "Please fix the problem and restart camonitorpv",
          MAX_EXCEPTIONS);
        ca_add_exception_event(NULL, NULL);
        return;
    }
    
    fprintf(stderr,"exCB Exception: Channel Access Exception:\n"
      "  Channel Name: %s\n"
      "  Native Type: %s\n"
      "  Native Count: %lu\n"
      "  Access: %s%s\n"
      "  IOC: %s\n"
      "  Message: %s\n"
      "  Context: %s\n"
      "  Requested Type: %s\n"
      "  Requested Count: %ld\n"
      "  Source File: %s\n"
      "  Line number: %u",
      args.chid?ca_name(args.chid):"Unavailable",
      args.chid?dbf_type_to_text(ca_field_type(args.chid)):"Unavailable",
      args.chid?ca_element_count(args.chid):0,
      args.chid?(ca_read_access(args.chid)?"R":""):"Unavailable",
      args.chid?(ca_write_access(args.chid)?"W":""):"",
      args.chid?ca_host_name(args.chid):"Unavailable",
      ca_message(args.stat)?ca_message(args.stat):"Unavailable",
      args.ctx?args.ctx:"Unavailable",
      dbf_type_to_text(args.type),
      args.count,
      args.pFile?args.pFile:"Unavailable",
      args.pFile?args.lineNo:0);
}
static void medmReplaceAccessRightsEventCb(
  struct access_rights_handler_args args)
{
    Channel *pCh = (Channel *) ca_puser(args.chid);
    Boolean readAccessChangedToRead = False;

  /* Increment the event counter */
    caTask.caEventCount++;

  /* Check for valid values */
    if(globalDisplayListTraversalMode != DL_EXECUTE) return;
    if(!pCh || !pCh->pr) {
	medmPostMsg(0,"medmReplaceAccessRightsEvent: "
	  "Invalid channel information\n");
	return;
    }

  /* Determine if the rights are changing from no read to read */
    if(!pCh->pr->readAccess && ca_read_access(pCh->chid)) {
	readAccessChangedToRead = True;
    }
  /* Change the access rights in the Record */
    pCh->pr->readAccess = ca_read_access(pCh->chid);
    pCh->pr->writeAccess = ca_write_access(pCh->chid);
    if(pCh->pr->updateValueCb)
      pCh->pr->updateValueCb((XtPointer)pCh->pr);

  /* Do a get as in medmConnectEventCb.  The get will cause the
     graphical info callback to be called.  This is necessary for some
     objects if the access rights are changing from no read to
     read. */
    if(pCh->pr->connected && pCh->previouslyConnected
      && readAccessChangedToRead) {
	int status = ca_array_get_callback(
	  dbf_type_to_DBR_CTRL(ca_field_type(args.chid)),
	  1, args.chid, medmUpdateGraphicalInfoCb, NULL);
	if(status != ECA_NORMAL) {
	    medmPostMsg(0,"medmReplaceAccessRightsEventCb: %s\n",
	      ca_message(status));
	}
    }
}
示例#6
0
文件: channel.c 项目: Cpppro/acquaman
/*
 * get the access rights:
 * note that even though there is separate read and write access, the rights
 * as of R3.14.1 are actually [ NONE, READ, and READ/WRITE]. If this changes,
 * things could get interesting.
 */
static void
getAccessRights( struct access_rights_handler_args args)
{
	Channel *chan = ca_puser(args.chid);
	Connector *conp;
	int readAccess, writeAccess;

	readAccess = ca_read_access(chan->chan_id);
	writeAccess = ca_write_access(chan->chan_id);
	chan->writeAccess = writeAccess?1:0;
	chan->readAccess = readAccess?1:0;

	DEBUGP printf("Channel %s read %d write %d\n", chan->chan_name, readAccess, writeAccess);
	/*
	 * user now allowed to write to this PV
	 */
	epicsMutexLock( processFlag);
	for( conp=chan->first_connector; conp; conp=conp->next_chan)
	    if( conp->newAccess)
		(*conp->newAccess)(conp);
	epicsMutexUnlock( processFlag);
	
}
示例#7
0
static void exceptionCallback(struct exception_handler_args args)
{
    const char *context = (args.ctx ? args.ctx : "unknown");

    errlogPrintf("DB CA Link Exception: \"%s\", context \"%s\"\n",
        ca_message(args.stat), context);
    if (args.chid) {
        errlogPrintf(
            "DB CA Link Exception: channel \"%s\"\n",
            ca_name(args.chid));
        if (ca_state(args.chid) == cs_conn) {
            errlogPrintf(
                "DB CA Link Exception:  native  T=%s, request T=%s,"
                " native N=%ld, request N=%ld, "
                " access rights {%s%s}\n",
                dbr_type_to_text(ca_field_type(args.chid)),
                dbr_type_to_text(args.type),
                ca_element_count(args.chid),
                args.count,
                ca_read_access(args.chid) ? "R" : "",
                ca_write_access(args.chid) ? "W" : "");
        }
    }
}
示例#8
0
long dbcar(char *precordname, int level)
{
    DBENTRY             dbentry;
    DBENTRY             *pdbentry=&dbentry;
    long                status;
    dbCommon            *precord;
    dbRecordType        *pdbRecordType;
    dbFldDes            *pdbFldDes;
    DBLINK              *plink;
    int                 ncalinks=0;
    int                 nconnected=0;
    int                 noReadAccess=0;
    int                 noWriteAccess=0;
    unsigned long       nDisconnect=0;
    unsigned long       nNoWrite=0;
    caLink              *pca;
    int                 j;

    if (!precordname || precordname[0] == '\0' || !strcmp(precordname, "*")) {
        precordname = NULL;
        printf("CA links in all records\n\n");
    } else {
        printf("CA links in record named '%s'\n\n", precordname);
    }
    dbInitEntry(pdbbase,pdbentry);
    status = dbFirstRecordType(pdbentry);
    while (!status) {
        status = dbFirstRecord(pdbentry);
        while (!status) {
            if (precordname ?
                !strcmp(precordname, dbGetRecordName(pdbentry)) :
                !dbIsAlias(pdbentry)) {
                pdbRecordType = pdbentry->precordType;
                precord = (dbCommon *)pdbentry->precnode->precord;
                for (j=0; j<pdbRecordType->no_links; j++) {
                    pdbFldDes = pdbRecordType->papFldDes[pdbRecordType->link_ind[j]];
                    plink = (DBLINK *)((char *)precord + pdbFldDes->offset);
                    dbLockSetGblLock();
                    if (plink->type == CA_LINK) {
                        ncalinks++;
                        pca = (caLink *)plink->value.pv_link.pvt;
                        if (pca
                        && pca->chid
                        && (ca_field_type(pca->chid) != TYPENOTCONN)) {
                            nconnected++;
                            nDisconnect += pca->nDisconnect;
                            nNoWrite += pca->nNoWrite;
                            if (!ca_read_access(pca->chid)) noReadAccess++;
                            if (!ca_write_access(pca->chid)) noWriteAccess++;
                            if (level>1) {
                                int rw = ca_read_access(pca->chid) |
                                         ca_write_access(pca->chid) << 1;
                                static const char *rights[4] = {
                                    "No Access", "Read Only",
                                    "Write Only", "Read/Write"
                                };
                                int mask = plink->value.pv_link.pvlMask;
                                printf("%28s.%-4s ==> %-28s  (%lu, %lu)\n",
                                    precord->name,
                                    pdbFldDes->name,
                                    plink->value.pv_link.pvname,
                                    pca->nDisconnect,
                                    pca->nNoWrite);
                                printf("%21s [%s%s%s%s] host %s, %s\n", "",
                                    mask & pvlOptInpNative ? "IN" : "  ",
                                    mask & pvlOptInpString ? "IS" : "  ",
                                    mask & pvlOptOutNative ? "ON" : "  ",
                                    mask & pvlOptOutString ? "OS" : "  ",
                                    ca_host_name(pca->chid),
                                    rights[rw]);
                            }
                        } else {
                            if (level>0) {
                                printf("%28s.%-4s --> %-28s  (%lu, %lu)\n",
                                    precord->name,
                                    pdbFldDes->name,
                                    plink->value.pv_link.pvname,
                                    pca->nDisconnect,
                                    pca->nNoWrite);
                            }
                        }
                    }
                    dbLockSetGblUnlock();
                }
                if (precordname) goto done;
            }
            status = dbNextRecord(pdbentry);
        }
        status = dbNextRecordType(pdbentry);
    }
done:
    if ((level > 1 && nconnected > 0) ||
        (level > 0 && ncalinks != nconnected)) printf("\n");
    printf("Total %d CA link%s; ",
           ncalinks, (ncalinks != 1) ? "s" : "");
    printf("%d connected, %d not connected.\n",
           nconnected, (ncalinks - nconnected));
    printf("    %d can't read, %d can't write.",
           noReadAccess, noWriteAccess);
    printf("  (%lu disconnects, %lu writes prohibited)\n\n",
           nDisconnect, nNoWrite);
    dbFinishEntry(pdbentry);
    
    if ( level > 2  && dbCaClientContext != 0 ) {
        ca_context_status ( dbCaClientContext, level - 2 );
    }

    return(0);
}
示例#9
0
文件: channel.c 项目: Cpppro/acquaman
/*
 * callback from connection handler: change the state of the channel valid
 * flag depending on the connection being up or down.
 * ASSUMPTIONS: chan_id is valid.
 */
static void
connect_callback(struct connection_handler_args args)
{
    Connector *conp;
    
    Channel *chan = ca_puser( args.chid);
    int status;
    
    DEBUGP printf("connect callback %p\n", chan);
    switch( args.op)
    {
    case CA_OP_CONN_UP:
    	if( chan->initData == 1)
	{
		chan->maxelement = ca_element_count(chan->chan_id);
		chan->chan_type = ca_field_type(chan->chan_id);
		chan->initData = 0;
	}
	/*
	 * note that a dropped and recreated connection can cause a free and alloc. This
	 * is not always redundant, as software may have restarted with different sizes
	 * for the field!
	 */
	if( chan->createData == 1 )
	{
		if( chan->dataval)
			free( chan->dataval);
		chan->dataval = calloc(dbr_size[chan->chan_type], chan->maxelement+1);
	}

	chan->readAccess = ca_read_access( chan->chan_id);
	chan->writeAccess = ca_write_access( chan->chan_id);
	status = ca_array_get_callback( dbf_type_to_DBR_CTRL(ca_field_type(chan->chan_id)), 1, chan->chan_id, getChanInfo, (void*)chan);
	SEVCHK(status, "connect_callback : ca_get_DBR_CTRL_event failed");
	status = ca_replace_access_rights_event( chan->chan_id, getAccessRights);
	SEVCHK(status, "connect_callback : ca_replace_access_rights_event failed");

	chan->valid = 1;
	chan->outstanding = 0;
	if( chan->monitor)
	{
	    chan->status = 100;	/* MAGIC: an absurd status */
	    status = ca_add_array_event(dbf_type_to_DBR_STS(chan->chan_type), chan->maxelement, chan->chan_id, monitorCallback, chan, 
		    		(double) 0.0, (double) 0.0, (double) 0.0, &(chan->eventId));
	    SEVCHK(status, "connect_callback : ca_add_event failed");
	    status = ca_flush_io();
	    SEVCHK(status, "connect_callback : ca_flush_io failed");
	}
	break;
	
    case CA_OP_CONN_DOWN:
	if( chan->valid && chan->eventId)
	    ca_clear_event(chan->eventId);
	chan->valid = 0;
	chan->status = 99;
	
	/*
	 * update the access rights and severity for all the connectors
	 */
	for(conp=chan->first_connector; conp ; conp=conp->next)
	{
	    if(conp->newAccess)
		(*conp->newAccess)(conp);
	    if( conp->newState)
	    	(*conp->newState)(conp);
	}
	
	break;
    default:
	break;
    }

    return;
}
示例#10
0
static void connectionCallback(struct connection_handler_args arg)
{
    caLink *pca;
    short link_action = 0;
    struct link *plink;

    pca = ca_puser(arg.chid);
    assert(pca);
    epicsMutexMustLock(pca->lock);
    plink = pca->plink;
    if (!plink) goto done;
    pca->isConnected = (ca_state(arg.chid) == cs_conn);
    if (!pca->isConnected) {
        struct pv_link *ppv_link = &plink->value.pv_link;
        dbCommon *precord = ppv_link->precord;

        pca->nDisconnect++;
        if (precord &&
            ((ppv_link->pvlMask & pvlOptCP) ||
             ((ppv_link->pvlMask & pvlOptCPP) && precord->scan == 0)))
            scanOnce(precord);
        goto done;
    }
    pca->hasReadAccess = ca_read_access(arg.chid);
    pca->hasWriteAccess = ca_write_access(arg.chid);

    if (pca->gotFirstConnection) {
        if (pca->nelements != ca_element_count(arg.chid) ||
            pca->dbrType != ca_field_type(arg.chid)) {
            /* BUG: We have no way to clear any old subscription with the
             *      originally chosen data type/size.  That will continue
             *      to send us data and will result in an assert() fail.
             */
            /* Let next dbCaGetLink and/or dbCaPutLink determine options */
            plink->value.pv_link.pvlMask &=
                ~(pvlOptInpNative | pvlOptInpString |
                  pvlOptOutNative | pvlOptOutString);

            pca->gotInNative  = 0;
            pca->gotOutNative = 0;
            pca->gotInString  = 0;
            pca->gotOutString = 0;
            free(pca->pgetNative); pca->pgetNative = 0;
            free(pca->pgetString); pca->pgetString = 0;
            free(pca->pputNative); pca->pputNative = 0;
            free(pca->pputString); pca->pputString = 0;
        }
    }
    pca->gotFirstConnection = TRUE;
    pca->nelements = ca_element_count(arg.chid);
    pca->dbrType = ca_field_type(arg.chid);
    if ((plink->value.pv_link.pvlMask & pvlOptInpNative) && !pca->pgetNative) {
        link_action |= CA_MONITOR_NATIVE;
    }
    if ((plink->value.pv_link.pvlMask & pvlOptInpString) && !pca->pgetString) {
        link_action |= CA_MONITOR_STRING;
    }
    if ((plink->value.pv_link.pvlMask & pvlOptOutNative) && pca->gotOutNative) {
        link_action |= CA_WRITE_NATIVE;
    }
    if ((plink->value.pv_link.pvlMask & pvlOptOutString) && pca->gotOutString) {
        link_action |= CA_WRITE_STRING;
    }
    pca->gotAttributes = 0;
    if (pca->dbrType != DBR_STRING) {
        link_action |= CA_GET_ATTRIBUTES;
    }
done:
    if (link_action) addAction(pca, link_action);
    epicsMutexUnlock(pca->lock);
}
static void medmConnectEventCb(struct connection_handler_args args) {
    int status;
    Channel *pCh = (Channel *)ca_puser(args.chid);
    int count;

  /* Increment the event counter */
    caTask.caEventCount++;

  /* Check for valid values */
    if(globalDisplayListTraversalMode != DL_EXECUTE) return;
    if(!pCh || !pCh->chid || !pCh->pr) {
	medmPostMsg(0,"medmConnectEventCb: Invalid channel information\n");
	return;
    }

  /* Do a get every time a channel is connected or reconnected and has
   * read access.  The get will cause the graphical info callback to
   * be called */
    if(args.op == CA_OP_CONN_UP && ca_read_access(pCh->chid)) {
	status = ca_array_get_callback(
	  dbf_type_to_DBR_CTRL(ca_field_type(args.chid)),
	  1, args.chid, medmUpdateGraphicalInfoCb, NULL);
	if(status != ECA_NORMAL) {
	    medmPostMsg(0,"medmConnectEventCb: "
	      "ca_array_get_callback [%s]:\n %s\n",
	      ca_name(pCh->chid)?ca_name(pCh->chid):"Unknown",
	      ca_message(status));
#if DEBUG_CONNECTION
# if 0
	    system("netstat | grep iocacis");
# endif
	    print("  pCh->chid %s args.chid\n",
	      pCh->chid == args.chid?"==":"!=");
	    print(
	      "  Channel Name: %s\n"
	      "  State: %s\n"
	      "  Native Type: %s\n"
	      "  Native Count: %hu\n"
	      "  Access: %s%s\n"
	      "  IOC: %s\n",
	      args.chid?ca_name(args.chid):"Unavailable",
	      args.chid?ca_state(args.chid) == cs_never_conn?"Never":
	      ca_state(args.chid) == cs_prev_conn?"Prev":
	      ca_state(args.chid) == cs_conn?"Conn":
	      ca_state(args.chid) == cs_closed?"Closed":"Unknown":"Unavailable",
	      args.chid?dbf_type_to_text(ca_field_type(args.chid)):"Unavailable",
	      args.chid?ca_element_count(args.chid):0,
	      args.chid?(ca_read_access(args.chid)?"R":"None"):"Unavailable",
	      args.chid?(ca_write_access(args.chid)?"W":""):"",
	      args.chid?ca_host_name(args.chid):"Unavailable");
#endif
	}
    }

  /* Handle four cases: connected or not and previously connected or not */
    if(args.op == CA_OP_CONN_UP) {
      /* Connected */
	if(pCh->previouslyConnected == False) {
	  /* Connected and not previously connected */
	  /* Set these first so they will be right for the updateValueCb */
	    pCh->pr->elementCount = ca_element_count(pCh->chid);
	    pCh->pr->dataType = ca_field_type(args.chid);
	    pCh->pr->connected = True;
	    caTask.channelConnected++;
	  /* Add the access-rights-change callback and the
	     significant-change (value, alarm or severity)
	     callback. Don't call the updateValueCb because
	     ca_replace_access_rights_event will call it. */
	    status = ca_replace_access_rights_event(
	      pCh->chid,medmReplaceAccessRightsEventCb);
	    if(status != ECA_NORMAL) {
		medmPostMsg(0,"medmConnectEventCb: "
		  "ca_replace_access_rights_event [%s]: %s\n",
		  ca_name(pCh->chid)?ca_name(pCh->chid):"Unknown",
		  ca_message(status));
	    }
	    if(pCh->pr->currentCount == -2) {
                count=0;
            } else {
                count=ca_element_count(pCh->chid);
            }
#ifdef __USING_TIME_STAMP__
	    status = ca_add_array_event(
	      dbf_type_to_DBR_TIME(ca_field_type(pCh->chid)),
	      count, pCh->chid,
	      medmUpdateChannelCb, pCh, 0.0,0.0,0.0, &(pCh->evid));
#else
	    status = ca_add_array_event(
	      dbf_type_to_DBR_STS(ca_field_type(pCh->chid)),
	      count, pCh->chid,
	      medmUpdateChannelCb, pCh, 0.0,0.0,0.0, &(pCh->evid));
#endif
	    if(status != ECA_NORMAL) {
	      /* Set the pointer to NULL in case CA didn't.  We don't
                 want to use it or clear it later. */
#if DEBUG_CONNECTION
		if(!pCh->evid) {
		    print("medmConnectEventCb: ca_add_array_event: \n"
		      "  status[%d] != ECA_NORMAL and pCh->evid != NULL\n",
		      status);
		}
#endif
		pCh->evid = NULL;
		medmPostMsg(0,"medmConnectEventCb: "
		  "ca_add_array_event [%s]:\n %s\n",
		  ca_name(pCh->chid)?ca_name(pCh->chid):"Unknown",
		  ca_message(status));
#if DEBUG_CONNECTION
# if 0
		system("netstat | grep iocacis");
# endif
		print("  pCh->chid %s args.chid\n",
		  pCh->chid == args.chid?"==":"!=");
		print(
		  "  Channel Name: %s\n"
		  "  State: %s\n"
		  "  Native Type: %s\n"
		  "  Native Count: %hu\n"
		  "  Access: %s%s\n"
		  "  IOC: %s\n",
		  args.chid?ca_name(args.chid):"Unavailable",
		  args.chid?ca_state(args.chid) == cs_never_conn?"Never":
		  ca_state(args.chid) == cs_prev_conn?"Prev":
		  ca_state(args.chid) == cs_conn?"Conn":
		  ca_state(args.chid) == cs_closed?"Closed":"Unknown":"Unavailable",
		  args.chid?dbf_type_to_text(ca_field_type(args.chid)):"Unavailable",
		  args.chid?ca_element_count(args.chid):0,
		  args.chid?(ca_read_access(args.chid)?"R":"None"):"Unavailable",
		  args.chid?(ca_write_access(args.chid)?"W":""):"",
		  args.chid?ca_host_name(args.chid):"Unavailable");
#endif
	    }
	  /* Set this one last so ca_replace_access_rights_event can
             use the old value */
	    pCh->previouslyConnected = True;
	} else {
	  /* Connected and previously connected */
	    pCh->pr->connected = True;
	    caTask.channelConnected++;
	    if(pCh->pr->updateValueCb)
	      pCh->pr->updateValueCb((XtPointer)pCh->pr);
	}
    } else {
      /* Not connected */
	if(pCh->previouslyConnected == False) {
	  /* Not connected and not previously connected */
	  /* Probably doesn't happen -- if CA can't connect, this
	   *   routine never gets called */
	    pCh->pr->connected = False;
	} else {
	  /* Not connected but previously connected */
	    pCh->pr->connected = False;
	    caTask.channelConnected--;
	    if(pCh->pr->updateValueCb)
	      pCh->pr->updateValueCb((XtPointer)pCh->pr);
	}
    }
}
static void pvInfoWriteInfo(void)
{
    time_t now;
    struct tm *tblock;
    char timeStampStr[TIME_STRING_MAX];
    Record *pR;
    chid chId;
    char *descVal;
#if defined(DBR_CLASS_NAME) && DO_RTYP
    char *rtypVal;
#endif
    DlElement *pE;
    char *elementType;
    static char noType[]="Unknown";
    struct dbr_time_string timeVal;
    char string[1024];     /* Danger: Fixed length */
    XmTextPosition curpos = 0;
    int i, j;

  /* Free the timeout */
    if(pvInfoTimerOn) {
	XtRemoveTimeOut(pvInfoTimeoutId);
	pvInfoTimerOn = False;
    }

  /* Just clean up and abort if we get called with no pvInfo
   *   (Shouldn't happen) */
    if(!pvInfo) return;

  /* Get timestamp */
    time(&now);
    tblock = localtime(&now);
    strftime(timeStampStr, TIME_STRING_MAX, STRFTIME_FORMAT"\n", tblock);
    timeStampStr[TIME_STRING_MAX-1]='\0';

  /* Get element type */
    pE = pvInfoElement;
    if(pE &&
      pE->type >= MIN_DL_ELEMENT_TYPE && pE->type <= MAX_DL_ELEMENT_TYPE) {
	elementType=elementType(pE->type);
    } else {
	elementType=noType;
    }

  /* Heading */
    sprintf(string, "           PV Information\n\nObject: %s\n%s\n",
      elementType, timeStampStr);
    XmTextSetInsertionPosition(pvInfoMessageBox, 0);
    XmTextSetString(pvInfoMessageBox, string);
    curpos+=strlen(string);

  /* Loop over the records to print information */
    for(i=0; i < nPvInfoPvs; i++) {
      /* Check for a valid record */
	if(!pvInfo[i].pvOk) continue;

	chId = pvInfo[i].pvChid;
	timeVal = pvInfo[i].timeVal;
	pR = pvInfo[i].record;

      /* Name */
	sprintf(string, "%s\n"
	  "======================================\n",
	  ca_name(chId));
      /* DESC */
	descVal = pvInfo[i].descVal;
	if(pvInfo[i].descOk && descVal) {
	    sprintf(string, "%sDESC: %s\n", string, descVal);
	} else {
	    sprintf(string, "%sDESC: %s\n", string, NOT_AVAILABLE);
	}
	if(pvInfo[i].descChid) ca_clear_channel(pvInfo[i].descChid);
#if defined(DBR_CLASS_NAME) && DO_RTYP
      /* RTYP */
	rtypVal = pvInfo[i].rtypVal;
	if(pvInfo[i].rtypOk && rtypVal && *rtypVal) {
	    sprintf(string, "%sRTYP: %s\n", string, rtypVal);
	} else {
	    sprintf(string, "%sRTYP: %s\n", string, NOT_AVAILABLE);
	}
#endif
      /* Items from chid */
	sprintf(string, "%sTYPE: %s\n", string,
	  dbf_type_to_text(ca_field_type(chId)));
      /* ca_element_count is defined differently in 3.14 vs. 3.13 */
	sprintf(string, "%sCOUNT: %lu\n", string,
	  (unsigned long)ca_element_count(chId));
	sprintf(string, "%sACCESS: %s%s\n", string,
	  ca_read_access(chId)?"R":"", ca_write_access(chId)?"W":"");
	sprintf(string, "%sIOC: %s\n", string, ca_host_name(chId));
	if(timeVal.value) {
	    char fracPart[10];

	  /* Do the value */
	    if(ca_element_count(chId) == 1) {
		sprintf(string, "%sVALUE: %s\n", string,
		  timeVal.value);
	    } else {
		sprintf(string, "%sFIRST VALUE: %s\n", string,
		  timeVal.value);
	    }
	  /* Convert the seconds part of the timestamp to UNIX time */
	    now = timeVal.stamp.secPastEpoch + 631152000ul;
	    tblock = localtime(&now);
	    strftime(timeStampStr, TIME_STRING_MAX, "%a %b %d, %Y %H:%M:%S",
	      tblock);
	    timeStampStr[TIME_STRING_MAX-1]='\0';
	  /* Get the fractional part.  This assumes strftime truncates
             seconds rather than rounding them, which seems to be the
             case. */
	    sprintf(fracPart, "%09d",timeVal.stamp.nsec);
	  /* Truncate to 3 figures */
	    fracPart[3]='\0';
	    sprintf(timeStampStr,"%s.%s", timeStampStr, fracPart);
	    timeStampStr[TIME_STRING_MAX-1]='\0';
	    sprintf(string, "%sSTAMP: %s\n", string, timeStampStr);
#if DEBUG_TIMESTAMP
	  /* This prints 9 significant figures and requires 3.13 base */
	    {
		char tsTxt[32];
		sprintf(string,"%sSTAMP: %s\n",string,
		  tsStampToText(&timeVal.stamp,TS_TEXT_MONDDYYYY,tsTxt));
	    }
#endif
	} else {
	    sprintf(string, "%sVALUE: %s\n", string, NOT_AVAILABLE);
	    sprintf(string, "%sSTAMP: %s\n", string, NOT_AVAILABLE);
	}
	XmTextInsert(pvInfoMessageBox, curpos, string);
	curpos+=strlen(string);

      /* Alarms */
	switch(pR->severity) {
	case NO_ALARM:
	    sprintf(string, "ALARM: NO\n");
	    break;
	case MINOR_ALARM:
	    sprintf(string, "ALARM: MINOR\n");
	    break;
	case MAJOR_ALARM:
	    sprintf(string, "ALARM: MAJOR\n");
	    break;
	case INVALID_ALARM:
	    sprintf(string, "ALARM: INVALID\n");
	    break;
	default:
	    sprintf(string, "ALARM: Unknown\n");
	    break;
	}
	XmTextInsert(pvInfoMessageBox, curpos, string);
	curpos+=strlen(string);

      /* Items from record */
	sprintf(string, "\n");
	switch(ca_field_type(chId)) {
        case DBF_STRING:
	    break;
        case DBF_ENUM:
	    sprintf(string, "%sSTATES: %d\n",
	      string, (int)(pR->hopr+1.1));
	  /* KE: Bad way to use a double */
	    if(pR->hopr) {
		for (j=0; j <= pR->hopr; j++) {
		    sprintf(string, "%sSTATE %2d: %s\n",
		      string, j, pR->stateStrings[j]);
		}
	    }
	    break;
        case DBF_CHAR:
        case DBF_INT:
        case DBF_LONG:
	    sprintf(string, "%sHOPR: %g  LOPR: %g\n",
	      string, pR->hopr, pR->lopr);
	    break;
        case DBF_FLOAT:
        case DBF_DOUBLE:
	    if(pR->precision >= 0 && pR->precision <= 17) {
		sprintf(string, "%sPRECISION: %d\n",
		  string, pR->precision);
	    } else {
		sprintf(string, "%sPRECISION: %d [Bad Value]\n",
		  string, pR->precision);
	    }
	    sprintf(string, "%sHOPR: %g  LOPR: %g\n",
	      string, pR->hopr, pR->lopr);
	    break;
        default:
	    break;
	}
	sprintf(string, "%s\n", string);
	XmTextInsert(pvInfoMessageBox, curpos, string);
	curpos+=strlen(string);
    }

  /* Pop it up unless we have left EXECUTE mode */
    if(globalDisplayListTraversalMode == DL_EXECUTE) {
	XtSetSensitive(pvInfoS, True);
	XtPopup(pvInfoS, XtGrabNone);
    }

  /* Free space */
    if(pvInfo) free((char *)pvInfo);
    pvInfo = NULL;
    pvInfoElement = NULL;
}