/* $Id$ * Author: Roger A. Cole * Date: 02-05-92 * * Copyright 1992, the Regents of the University of California, * and the University of Chicago Board of Governors. * * This software was produced under U.S. Government contract: * (W-7405-ENG-36) at the Los Alamos National Laboratory. * * Modification Log: * ----------------- * .01 02-05-92 rac initial version * .02 02-13-92 rac allow specifying an optional set of parameters * */ /*+/mod*********************************************************************** * TITLE recListProg.c - list information about EPICS database records * * DESCRIPTION * List information about one or several database records. The * record names may be specified in a file or on the command line. * The information can be obtained from IOCs, or from DCT short * report files. When using short reports, either a specific short * report or a file containing a list of paths to short reports can * be used. * * USAGE * % recListProg [options] [recName recName ...] * * where options are: * -I inFile specifies a path to a file containing a * list of record names. If this option * is used, any recName on command line * are ignored * -O outFile * -D dctPathFile specifies either a short report or a file * containing a list of paths to short reports. * -f field specifies a field name to add to the list * of fields to print, as DBF_FLOAT; if `field' * is `clear', then all "canned" fields are * forgotten and only caller-specified fields * are listed. * -s field specifies a field name to add to the list * of fields to print, as DBF_SHORT; the special * field name `clear' can be used as for -f * * BUGS * o doesn't get record type or host name when getting information from * the IOCs via Channel Access * o doesn't allow specifying record type (plain or wildcard) (e.g., * looking for all mbbo records) * o Channel Access status messages (, , etc.) are mixed * in with output. (This is really a CA bug, since they can't be * suppressed and they go to stdout.) * o doesn't handle state records, enum fields, etc., gracefully * o doesn't handle array fields well * *-***************************************************************************/ #include #include #include #include #include #include #if 0 #include "iocmsg.h" #include "iocinf.h" extern struct ca_static *ca_static; #define iiu (ca_static->ca_iiu) if (i == 0 && pCh != NULL && dbf_type_is_valid(ca_field_type(pCh))) { char *x; x = host_from_addr(&iiu[pCh->iocix].sock_addr.sin_addr); x = host_from_addr(&iiu[0].sock_addr.sin_addr); strncpy(info[i].hostName, host_from_addr(&iiu[pCh->iocix].sock_addr.sin_addr), 8); printf("\n%s\n", info[i].hostName); } #endif /* nint, exp10 aren't ANSI-C or POSIX */ # ifndef sun # ifndef nint # define nint(value) (value>=0 ? (int)((value)+.5) : (int)((value)-.5)) # endif # ifndef exp10 # define exp10(value) (exp(value * log(10.))) # endif # endif #define FLD_DIM 20 static int nFld; static char *(fields[FLD_DIM]); static chtype types[FLD_DIM]; static int notInDct[FLD_DIM]; static int notInField[FLD_DIM]; static char dctValues[FLD_DIM][100]; /* values for the fields above */ static char dctName[100]; static char dctType[100]; static char dctNode[100]; #define DCT_DIM 200 static struct { char fldName[db_name_dim]; /* actual fields in the records */ char fldVal[100]; } dctFields[DCT_DIM]; static int nDctFields; /* number of fields for record just read */ typedef struct { int inIOC; int inDCT; chid pCh; char name[db_name_dim]; char val[db_strval_dim]; short shtval; float fltval; chtype type; } CHAN; typedef struct rec { char name[db_name_dim]; /* gotten from user; may have .FLD */ char recName[db_name_dim]; /* stripped of .FLD */ char recType[db_name_dim]; /* type of record */ int aField; /* 1 if name had a .FLD */ char fldName[db_name_dim]; /* actual field name if `aField' */ char filled; char hostName[8]; int found; /* 1 if record has been found */ int hasWild; /* 1 if name has wildcard char's */ struct rec *pHead; struct rec *pTail; struct rec *pNext; struct rec *pPrev; CHAN info[FLD_DIM]; } REC; #define REC_DIM 200 static REC records[REC_DIM];/* records to be found */ static int nRec=0; /* number of records to be found */ static int allFound; /* all requested records have been found */ /*+/subr********************************************************************** * NAME main * *-*/ main(argc, argv) int argc; /* number of command line args */ char *argv[]; /* command line args */ { FILE *pInFile, *pOutFile, *pDctFile; char filled; char rec[db_name_dim]; int c, i, argPtr; int header=1; int cmdLineMode=1; int dctFlag=0; nFld = 0; initFields(); pInFile = stdin; pOutFile = stdout; for (argPtr=1; argPtr= REC_DIM) { printf("too many records\n"); return 1; } if (strcmp(argv[argPtr], "F") == 0) { records[nRec].filled = 'F'; argPtr++; if (argPtr >= argc) break; } else records[nRec].filled = ' '; strncpy(records[nRec].name, argv[argPtr], db_name_dim); nRec++; } } else { while (getFieldFromStream(pInFile, rec, &filled) != EOF) { if (strncmp(rec, "NAME:", 5) == 0) { while (getFieldFromStream(pInFile, rec, &filled) != EOF) { if (strcmp(rec, "%endHeader%") == 0) { getFieldFromStream(pInFile, rec, &filled); break; } } } if (nRec >= REC_DIM) { printf("too many records\n"); return 1; } records[nRec].filled = filled; strncpy(records[nRec].name, rec, db_name_dim); nRec++; } } for (i=0; i= 0; nFld++) { assert(nFld < FLD_DIM); fields[nFld] = f[nFld]; types[nFld] = t[nFld]; notInDct[nFld] = notDct[nFld]; notInField[nFld] = notField[nFld]; } } /*+/subr********************************************************************** * NAME setUpRec - initialize the structure for a record * *-*/ setUpRec(pRec, dctFlag) REC *pRec; int dctFlag; { char *dot, *wild; int aField; int i; strcpy(pRec->recName, pRec->name); strcpy(pRec->recType, "."); strcpy(pRec->hostName, "notFound"); pRec->pHead = NULL; pRec->pTail = NULL; if ((dot = strchr(pRec->recName, '.')) != NULL) { *dot = '\0'; /* truncate pRec->recName at the '.' */ if (strchr(dot+1, '*') != NULL || strchr(dot+1, '?') != NULL) printf("\n*** wildcards in field name won't be acted on\n"); if (strcmp(dot+1, "VAL") != 0) { pRec->aField = aField = 1; strcpy(pRec->fldName, dot+1); } else { pRec->aField = aField = 0; strcpy(pRec->fldName, "."); } } else { pRec->aField = aField = 0; strcpy(pRec->fldName, "."); } pRec->found = 0; if ((wild = strchr(pRec->recName, '*')) == NULL) wild = strchr(pRec->recName, '?'); if (wild != NULL) { if (dctFlag) { strcpy(pRec->recType, "wild"); pRec->hasWild = 1; } else printf("\n*** wildcards in record name won't be acted on\n"); } for (i=0; iaField) strcpy(pRec->info[i].name, pRec->name); else if (fields[i] != '\0') sprintf(pRec->info[i].name, "%s.%s", pRec->recName, fields[i]); else strcpy(pRec->info[i].name, pRec->name); pRec->info[i].pCh = NULL; pRec->info[i].type = types[i]; pRec->info[i].inIOC = pRec->info[i].inDCT = 1; if (dctFlag) { strcpy(pRec->info[i].val, "."); if (notInDct[i]) pRec->info[i].inDCT = 0; } else if (aField && notInField[i]) { strcpy(pRec->info[i].val, "n/a"); pRec->info[i].inIOC = 0; } else strcpy(pRec->info[i].val, "bad!"); } } /*+/subr********************************************************************** * NAME getInfo_DCT - get information from DCT * * DESCRIPTION * Gets information from DCT. This routine will process either * 1) a file containing a list of white-space-separated path names * for DCT short reports; or 2) an actual DCT short report. * *-*/ getInfo_DCT(pDctFile) FILE *pDctFile; /* I control file or short report */ { FILE *pFile; char file[100]; while (getFieldFromStream(pDctFile, file, NULL) != EOF) { if (strcmp(file, "$$mono") == 0) { getInfo_DCTfile(pDctFile); break; } if ((pFile = fopen(file, "r")) == NULL) { printf("couldn't open %s\n", file); perror(""); return 1; } getInfo_DCTfile(pFile); fclose(pFile); if (allFound) break; } } /*+/subr********************************************************************** * NAME getInfo_DCTfile - process a DCT short report * *-*/ getInfo_DCTfile(pFile) FILE *pFile; { int rec, fld; int stat; int i; int c; char field[100]; char value[100]; int gotOne=0; while (1) { c = getFieldFromStream(pFile, field, NULL); if (c == EOF || strcmp(field, "PV:") == 0) { if (gotOne) getInfo_DCT_work(); allFound = 1; for (i=0; iname, dctName); else sprintf(pRec->name, "%s.%s", dctName, records[rec].fldName); setUpRec(pRec, 1); pRec->filled = records[rec].filled; DoubleListAppend(pRec, records[rec].pHead, records[rec].pTail); } else pRec = &records[rec]; strcpy(pRec->hostName, dctNode); strcpy(pRec->recType, dctType); if (pRec->aField) { strcpy(pRec->info[0].val, "unknown"); for (i=0; ifldName) == 0) { strcpy(pRec->info[0].val, dctFields[i].fldVal); break; } } } else strcpy(pRec->info[0].val, "."); if (pRec->hasWild == 0) pRec->found = 1; for (fld=1; fldaField && notInField[fld]) ; /* no action */ else { strcpy(pRec->info[fld].val, dctValues[fld]); if (types[fld] == DBR_SHORT) sscanf(dctValues[fld], "%hd", &pRec->info[fld].shtval); else if (types[fld] == DBR_FLOAT) sscanf(dctValues[fld], "%f", &pRec->info[fld].fltval); } } } } } /*+/subr********************************************************************** * NAME getInfo_IOC - get information from IOCs * *-*/ getInfo_IOC() { int rec, fld; int stat; int i; chid pCh; /*----------------------------------------------------------------------------- * make the connections *----------------------------------------------------------------------------*/ for (rec=0; recpHead) != NULL) { while (pR != NULL) { fprintf(pOutFile, "%-8s %4.4s %30s %c %8s", pR->hostName, pR->recType, pR->name, pR->filled, pR->info[0].val); for (i=1; iinfo[i].fltval, 3); fprintf(pOutFile, " %4.4s", fltText); } else fprintf(pOutFile, " %4.4s", pR->info[i].val); } } fprintf(pOutFile, "\n"); pR = pR->pNext; } return; } fprintf(pOutFile, "%-8s %4.4s %30s %c %8s", pRec->hostName, pRec->recType, pRec->name, pRec->filled, pRec->info[0].val); for (i=1; iinfo[i].fltval, 3); fprintf(pOutFile, " %4.4s", fltText); } else fprintf(pOutFile, " %4.4s", pRec->info[i].val); } } fprintf(pOutFile, "\n"); return; } if (header) { fprintf(pOutFile, "\n%30s F %8s", "name", "value"); for (i=1; iname, pRec->filled, pRec->info[0].val); for (i=1; iinfo[i].val, "bad!") == 0 || strcmp(pRec->info[i].val, ".") == 0 || strcmp(pRec->info[i].val, "n/a") == 0) fprintf(pOutFile, " %4.4s", pRec->info[i].val); else if (types[i] == DBR_FLOAT) { cvtDblToTxt(fltText, 4, pRec->info[i].fltval, 3); fprintf(pOutFile, " %4.4s", fltText); } else fprintf(pOutFile, " %4d", pRec->info[i].shtval); } fprintf(pOutFile, "\n"); } /*+/subr********************************************************************** * NAME getFieldFromStream - get next name from file * * DESCRIPTION * Gets the next white-space-delimited field from a FILE* stream. * Blank lines are ignored. The `#' character starts a comment-- * all the rest of a line is ignored. The `#' also serves as * a delimiter for a field. * * If the pFilled argument isn't NULL, then a field of "F" is * treated special: It is taken to be a modifier for the following * field. * * RETURNS * delimiter, or * EOF * *-*/ getFieldFromStream(pFile, pName, pFilled) FILE *pFile; char *pName; char *pFilled; { char *name=pName; int c; char filled; *name = '\0'; filled = ' '; while ((c = fgetc(pFile)) != EOF) { if (!isascii(c)) break; if (isspace(c)) { if (name == pName) ; /* skip over white space */ else if (strcmp(pName, "F") == 0) { if (pFilled == NULL) break; filled = 'F'; name = pName; *name = '\0'; } else break; } else if (c == '#') { while ((c = fgetc(pFile)) != EOF && c != '\n') ; /* keep skipping until \n */ if (name != pName) break; } else { *(name++) = c; *name = '\0'; } } if (name == pName) return EOF; if (pFilled != NULL) *pFilled = filled; return c; } /*+/subr********************************************************************** * NAME cvtDblToTxt - convert double to text, being STINGY with space * * DESCRIPTION * Convert a double value to text. The main usefulness of this routine * is that it maximizes the amount of information presented in a * minimum number of characters. For example, if a 1 column width * is specified, only the sign of the value is presented. * * When an exponent is needed to represent the value, for narrow * column widths only the exponent appears. If there isn't room * even for the exponent, large positive exponents will appear as E*, * and large negative exponents will appear as E-. * * Negative numbers receive some special treatment. In narrow * columns, very large negative numbers may be represented as -* and * *-*/ static cvtDblToTxt(text, width, value, decPl) char *text; /* O text representation of value */ int width; /* I max width of text string (not counting '\0') */ double value; /* I value to print */ int decPl; /* I max # of dec places to print */ { double valAbs; /* absolute value of caller's value */ int wholeNdig; /* number of digits in "whole" part of value */ double logVal; /* log10 of value */ int decPlaces; /* number of decimal places to print */ int expI; /* exponent for frac values */ double expD; int expWidth; /* width needed for exponent field */ int excess; /* number of low order digits which won't fit into the field */ char tempText[100]; /* temp for fractional conversions */ int roomFor; int minusWidth; /* amount of room for - sign--0 or 1 */ /*----------------------------------------------------------------------------- * special cases *----------------------------------------------------------------------------*/ if (value == 0.) { (void)strcpy(text, "0"); return; } else if (value == 1.) { (void)strcpy(text, "1"); return; } if (width == 1) { if (value >= 0) strcpy(text, "+"); else strcpy(text, "-"); return; } else if (width == 2 && value <= -1.) { if (value == -1.) strcpy(text, "-1"); else strcpy(text, "-"); return; } valAbs = value>0. ? value : -value; logVal = log10(valAbs); if (logVal < 0.) { /*----------------------------------------------------------------------------- * numbers with only a fractional part *----------------------------------------------------------------------------*/ if (width == 2) { if (value > 0.) strcpy(tempText, "0E-"); else strcpy(tempText, "0-."); } else if (width == 3 && value < 0) strcpy(tempText, "0-."); else { if (value < 0.) minusWidth = 1; else minusWidth = 0; expI = -1 * (int)logVal; if (expI < 9) expWidth = 3; /* need E-n */ else if (expI < 99) expWidth = 4; /* need E-nn */ else if (expI < 999) expWidth = 5; /* need E-nnn */ else expWidth = 6; /* need E-nnnn */ /* figure out how many sig digit can appear if print "as is" */ /* expI is the number of leading zeros */ roomFor = width - expI - 1 - minusWidth; /* -1 for the dot */ if (roomFor >= 1) { decPlaces = expI + decPl; if (decPlaces > width -1 - minusWidth) decPlaces = roomFor + expI; (void)sprintf(tempText, "%.*f", decPlaces, value); if (value < 0.) tempText[1] = '-'; } else { expD = expI; value *= exp10(expD); if (value > 0.) sprintf(tempText, "0.E-%d", expI); else sprintf(tempText, "--.E-%d", expI); } } strncpy(text, &tempText[1], width); text[width] = '\0'; return; } /*----------------------------------------------------------------------------- * numbers with both an integer and a fractional part * * find out how many columns are required to represent the integer part * of the value. A - is counted as a column; the . isn't. *----------------------------------------------------------------------------*/ wholeNdig = 1 + (int)logVal; if (value < 0.) wholeNdig++; if (wholeNdig < width-1) { /*----------------------------------------------------------------------------- * the integer part fits well within the field. Find out how many * decimal places can be printed (honoring caller's decPl limit). *----------------------------------------------------------------------------*/ decPlaces = width - wholeNdig - 1; if (decPl < decPlaces) decPlaces = decPl; if (decPl > 0) (void)sprintf(text, "%.*f", decPlaces, value); else (void)sprintf(text, "%d", nint(value)); } else if (wholeNdig == width || wholeNdig == width-1) { /*----------------------------------------------------------------------------- * The integer part just fits within the field. Print the value as an * integer, without printing the superfluous decimal point. *----------------------------------------------------------------------------*/ (void)sprintf(text, "%d", nint(value)); } else { /*----------------------------------------------------------------------------- * The integer part is too large to fit within the caller's field. Print * with an abbreviated E notation. *----------------------------------------------------------------------------*/ expWidth = 2; /* assume that En will work */ excess = wholeNdig - (width - 2); if (excess > 999) { expWidth = 5; /* no! it must be Ennnn */ excess += 3; } else if (excess > 99) { expWidth = 4; /* no! it must be Ennn */ excess += 2; } else if (excess > 9) { expWidth = 3; /* no! it must be Enn */ excess += 1; } /*----------------------------------------------------------------------------- * Four progressively worse cases, with all or part of exponent fitting * into field, but not enough room for any of the value * Ennn positive value; exponent fits * -Ennn negative value; exponent fits * +**** positive value; exponent too big * -**** negative value; exponent too big *----------------------------------------------------------------------------*/ if (value >= 0. && expWidth == width) (void)sprintf(text, "E%d", nint(logVal)); else if (value < 0. && expWidth == width-1) (void)sprintf(text, "-E%d", nint(logVal)); else if (value > 0. && expWidth > width) (void)sprintf(text, "%.*s", width, "+*******"); else if (value < 0. && expWidth > width-1) (void)sprintf(text, "%.*s", width, "-*******"); else { /*----------------------------------------------------------------------------- * The value can fit, in exponential notation *----------------------------------------------------------------------------*/ (void)sprintf(text, "%dE%d", nint(value/exp10((double)excess)), excess); } } }