From pds@fsl.noaa.gov Fri Oct 1 22:19:49 1999 Received: from gatekeeper.fsl.noaa.gov (gatekeeper.fsl.noaa.gov [137.75.1.181]) by brain.fsl.noaa.gov (8.8.5/8.8.5) with ESMTP id WAA02237 for ; Fri, 1 Oct 1999 22:19:49 GMT Received: from dilbert.fsl.noaa.gov (pds@dilbert.fsl.noaa.gov [137.75.132.102]) by gatekeeper.fsl.noaa.gov (8.9.1/8.9.1) with ESMTP id WAA26974 for ; Fri, 1 Oct 1999 22:19:49 GMT Received: (from pds@localhost) by dilbert.fsl.noaa.gov (8.9.1/8.9.1) id WAA12301 for albers; Fri, 1 Oct 1999 22:19:48 GMT Date: Fri, 1 Oct 1999 22:19:48 GMT From: PDS Librarian Message-Id: <199910012219.WAA12301@dilbert.fsl.noaa.gov> Content-Type: text Status: OR /*------------------------------------------------------------------------------ ** NOAA/ERL ** Forecast Systems Laboratory ** Facility Division ** Remote Sensor Systems Branch ** ** This software and its documentation are in the public domain and are ** furnished "as is". The United States government, its instrumentalities, ** officers, employees, and agents make no warranty, express or implied, as to ** the usefullness of the software and documentation for any purpose. They ** assume no responsibility (1) for the use of the software and documentation; ** or (2) to provide technical support to users. ** ** getRdamData.c ** ** This module contains the routines to search and read NIMBUS pool files ** containing translator and data notifications, and RDAM data. ** ** WARNING: This is OPAQUE Facility Division (FD) Software. All OPAQUE FD ** Software is subject to change without notice. ** ** 01/23/97 Dell Long v1.1 Original version **----------------------------------------------------------------------------*/ static char RcsId[] = "$Id: getRdamData.c,v 1.1 1997/03/13 22:40:22 pds Exp $"; #include #include #include #include /*---------------------------------------------------------------------------- ** getTriggerRadialData() ** Purpose: Reads the DAM/RDAM structure pointed to by the arguments. ** Arguments: ** poolPath - Path of the pool file. ** poolPosition - Position of DAM data in the pool file. ** poolLength - Length of the DAM data. ** dam - Pointer to the DAM data read from the pool file. ** ** Returns: EXIT_SUCCESS, EXIT_FAILURE **--------------------------------------------------------------------------*/ int getTriggerRadialData(char *poolPath, long poolPosition, long poolLength, DamDataStruct **dam) { char *record; char timeStr[9]; int dbHandle, status; long damSize; time_t frameTime; struct tm timeStruct; NotifyArgs *poolInfo; RdamDataStruct *rdam; poolInfo = (NotifyArgs *) calloc(1, sizeof(NotifyArgs)); if (poolInfo == (NotifyArgs *)NULL) { PANIC; } /*-------------------------------------------------------------------------- ** Open and read the given database. **------------------------------------------------------------------------*/ dbHandle = open(poolPath, O_RDONLY); if (dbHandle < 0) { PdsAdvise (Targ, PdsError, "ERROR: Could not open %s on %s!\n\n", poolPath, strerror(errno)); return OS_FUNCTION_ERROR; } /*-------------------------------------------------------------------------- ** Record pool information into the poolInfo (NotifyArgs) structure. **------------------------------------------------------------------------*/ poolInfo->Var.Data.Pool.Path = poolPath; poolInfo->Var.Data.Pool.Pos = poolPosition; poolInfo->Var.Data.Pool.Length = poolLength; /*-------------------------------------------------------------------------- ** Allocate a buffer to hold an appropriately sized record. **------------------------------------------------------------------------*/ record = (char *) calloc(1, poolInfo->Var.Data.Pool.Length); if (record == (char *)NULL) { PANIC; } /*-------------------------------------------------------------------------- ** Get the message content. **------------------------------------------------------------------------*/ if (getPoolData(dbHandle, poolInfo, record) == EXIT_FAILURE) { free(record); PdsAdvise (Targ, PdsError, "Could not read pool data"); return EXIT_FAILURE; } free(poolInfo); close(dbHandle); /*-------------------------------------------------------------------------- ** XDR decode the RDAM data **------------------------------------------------------------------------*/ status = SerializeDamDataStruct(*dam, damSize, SER_DECODE, &record, (long *) &poolLength); if (status != EXIT_SUCCESS) { free(record); PdsAdvise(Targ, PdsError, "Could not decode data"); return EXIT_FAILURE; } rdam = (RdamDataStruct *)(*dam)->classInstance; /*-------------------------------------------------------------------------- ** Make sure that this is indeed the last ray of an elevation or volume ** scan message by checking the message type and (if it _is_ a ray) the ** ray type. **------------------------------------------------------------------------*/ if (rdam->data.hdr.msg_type == WSR88D_DRD) { if (rdam->data.msg.drd->ray_status != END_ELEVATION &&rdam->data.msg.drd->ray_status != BAD_END_ELEVATION && rdam->data.msg.drd->ray_status != END_VOLUME_SCAN && rdam->data.msg.drd->ray_status != BAD_END_VOLUME_SCAN) { free(record); PdsAdvise(Targ, PdsError, "Triggering RDAM message is radial data, " "but not the last ray of a sweep"); return EXIT_FAILURE; } } else { free(record); PdsAdvise(Targ, PdsError, "Triggering RDAM message is not radial data"); return EXIT_FAILURE; } free(record); return EXIT_SUCCESS; } /* getTriggerRadialData() */ /*---------------------------------------------------------------------------- ** findXltNtyDBs() ** Purpose: Finds translator notifications matching the search criteria ** given by the arguments. ** Arguments: ** xltNtyList - List of matching translator notifications. ** xltNtyPath - Path of where to find translator notification databases. ** xltNtyKey - Regular expression of matching translator key. ** startTime - Start Time of the sweep in which we are interested. ** elevation - Elevation number of the sweep in which we are interested. ** numValidXltNty - Number of matching translator notifications. ** ** Returns: EXIT_SUCCESS, EXIT_FAILURE **--------------------------------------------------------------------------*/ int findXltNtyDBs(NotifyArgs ***xltNtyList, char *xltNtyPath, char *xltNtyKey, time_t startTime, int elevation, short *numValidXltNty) { int i, status; char **xltNtyDbList; char *record; int numDbFiles; PdsDataKey **ntyKeyList, *tempKey; int dbIndex, keyIndex; u_long dbHandle; u_long maxRecLen, oldMaxRecLen = 0; u_long maxDbKeys; time_t curElevStartTime; time_t tempTime = 0; int numNtyKeyMatches = 0; NotifyArgs *curNty; DescripTypes type; int *curElevation = NULL; xltNtyDbList = (char **) calloc(2, sizeof(char **)); if (xltNtyDbList == (char **)NULL) PANIC; record = (char *) calloc(1, 1); if (record == NULL) PANIC; *numValidXltNty = 0; if ((numDbFiles = findNotifyDBs(xltNtyPath, startTime, &xltNtyDbList)) == 0) { PdsAdvise(Targ, PdsError, "No pool files were found"); free(xltNtyDbList); free(record); return EXIT_FAILURE; } /*-------------------------------------------------------------------------- ** Search for and record valid translator notifications. **------------------------------------------------------------------------*/ if (Verbose == TRUE) { PdsAdvise(Targ, PdsUp, "Going into loop to search for valid " "translator notifications.\n"); } /*-------------------------------------------------------------------------- ** Prime ntyKeyList for future realloc's. **------------------------------------------------------------------------*/ ntyKeyList = (PdsDataKey **) calloc(1, sizeof(PdsDataKey *)); if (ntyKeyList == (PdsDataKey **) NULL) PANIC; /*-------------------------------------------------------------------------- ** Loop to examine the translator notification database list. (In ** reality this is no longer a list). **------------------------------------------------------------------------*/ for (dbIndex = 0; xltNtyDbList[dbIndex] != (char *)NULL; dbIndex++) { /*---------------------------------------------------------------------- ** open a database for search **--------------------------------------------------------------------*/ if (PdsOpenDtb(xltNtyDbList[dbIndex], &dbHandle, Readonly) != 0) { PdsAdvise(Targ, PdsError, "ERROR while opening %s -- %s", xltNtyDbList[dbIndex], strerror(errno)); for (dbIndex = 0; xltNtyDbList[dbIndex] != (char *)NULL; dbIndex++) { free(xltNtyDbList[dbIndex]); } free(xltNtyDbList); free(record); free(ntyKeyList); return EXIT_FAILURE; } else { if (Verbose) PdsAdvise(Targ, PdsUp, "Examining translator notifications in %s", xltNtyDbList[dbIndex]); } /*---------------------------------------------------------------------- ** Get the max data record length in this database **--------------------------------------------------------------------*/ if (MaxDataLen(dbHandle, &maxRecLen) != EXIT_SUCCESS) { PdsAdvise(Targ, PdsError, "Can't get maximum record length"); for (dbIndex = 0; xltNtyDbList[dbIndex] != (char *)NULL; dbIndex++) free(xltNtyDbList[dbIndex]); free(xltNtyDbList); free(record); free(ntyKeyList); return EXIT_FAILURE; } /*---------------------------------------------------------------------- ** Allocate a buffer to hold the max record **----------------------------------------------------------------*/ if (oldMaxRecLen != maxRecLen) { record = (char *) realloc(record, (size_t)maxRecLen + 1); if (record == (char *)NULL) { PANIC; } oldMaxRecLen = maxRecLen; } /*---------------------------------------------------------------------- ** Get the number of keys in this database. **--------------------------------------------------------------------*/ if (NumDtbKeys(dbHandle, &maxDbKeys) != EXIT_SUCCESS) { PdsAdvise(Targ, PdsError, "Could not get the number of data keys"); for (dbIndex = 0; xltNtyDbList[dbIndex] != (char *)NULL; dbIndex++) { free(xltNtyDbList[dbIndex]); } free(xltNtyDbList); free(record); free(ntyKeyList); return EXIT_FAILURE; } ntyKeyList = (PdsDataKey **) realloc(ntyKeyList, maxDbKeys * sizeof(PdsDataKey *)); if (ntyKeyList == (PdsDataKey **) NULL) { PANIC; } /*---------------------------------------------------------------------- ** Loop through the keys finding ones that are of interest (match the ** target). No sense reading & decoding data we aren't interested in. **--------------------------------------------------------------------*/ if (Verbose == TRUE) PdsAdvise(Targ, PdsUp, "Looping through the keys and finding ones " "that are of interest.\n"); for (i = 0; i < maxDbKeys; i++) { /*---------------------------------------------------------------------- ** Get the next key **--------------------------------------------------------------------*/ if (GetKey(dbHandle, i, &tempKey) != 0) break; if (matchPdsDataKey(xltNtyKey, tempKey) == 0) ntyKeyList[numNtyKeyMatches++] = tempKey; #ifdef DEBUG else { PdsAdvise(Targ, PdsUp, "no match: %s", ((char *) tempKey) + sizeof(PdsDataKey)); PdsAdvise(Targ, PdsUp, "Match attempts: %d Matches: " "%d", i + 1, numNtyKeyMatches); } #endif } if (Verbose == TRUE) { PdsAdvise(Targ, PdsUp, "Found %d matches.", numNtyKeyMatches); PdsAdvise(Targ, PdsUp, "Loop through the keys that are " "interesting based on their matching the target.\n"); } for (keyIndex = 0; keyIndex < numNtyKeyMatches; keyIndex++) { /*------------------------------------------------------------------ ** Get the message content **----------------------------------------------------------------*/ status = GetData(dbHandle, ntyKeyList[keyIndex], record); if (status != EXIT_SUCCESS) { PdsAdvise(Targ, PdsError, "ERROR: Could not read data in function GetData."); for (dbIndex = 0; xltNtyDbList[dbIndex] != (char *)NULL; dbIndex++) { free(xltNtyDbList[dbIndex]); } free(xltNtyDbList); for (i = 0; i < numNtyKeyMatches; i++) free(ntyKeyList[i]); free(ntyKeyList); free(record); return EXIT_FAILURE; } curNty = (NotifyArgs *) calloc(1, sizeof(NotifyArgs)); if (curNty == (NotifyArgs *)NULL) { PANIC; } /*------------------------------------------------------------------ ** XDR decode the notification message. **----------------------------------------------------------------*/ (void)DecodeNotify(record, curNty); #ifdef DEBUG PdsAdvise(Targ, PdsUp, "Examining notification %s...", curNty->Var.Xlt.xltKey); #endif /*------------------------------------------------------------------ ** Confirm that this is a translator notification. If not then try ** the next one. **------------------------------------------------------------*/ if (curNty->Notify.Type != XLT_NOTIFY) { PdsAdvise(Targ, PdsError, "Config error. File %s contains " "non-translator notifications.\n", xltNtyDbList[dbIndex]); free(curNty); continue; } /*------------------------------------------------------------------ ** Confirm that the elevation start time is correct. If not try next. **----------------------------------------------------------------*/ curElevStartTime = GetDataTime("Elevation Start Time", &curNty->Var.Xlt.dataTimes); if (curElevStartTime != startTime) { if (tempTime != curElevStartTime) tempTime = curElevStartTime; free(curNty); continue; } /*------------------------------------------------------------------ ** Confirm that the ray is in the correct elevation. ** If it isn't then try the next one. **----------------------------------------------------------------*/ type = Int; status = GetProductDescription("Elevation Number", &type, (char **) &curElevation, &curNty->Var.Xlt.prodDesc); if (status == EXIT_SUCCESS) { if (*curElevation != elevation) { free(curNty); continue; } } else { #ifdef DEBUG PdsAdvise(Targ, PdsError, "Product description %s not found in " "XltNotify.", "Elevation Number"); #endif free(curNty); continue; } /*------------------------------------------------------------------ ** This notification meets all acceptance criteria. **----------------------------------------------------------------*/ (*numValidXltNty)++; #ifdef DEBUG PdsAdvise(Targ, PdsUp, "Adding translator notification %02d -- %s " "to list...", *numValidXltNty, curNty->Var.Xlt.xltKey); #endif *xltNtyList = (NotifyArgs **) realloc(*xltNtyList, *numValidXltNty * sizeof(NotifyArgs *)); if (*xltNtyList == (NotifyArgs **) NULL) { PANIC; } (*xltNtyList)[*numValidXltNty - 1] = curNty; } /*---------------------------------------------------------------------- ** Clean up for next database. **--------------------------------------------------------------------*/ numNtyKeyMatches = 0; PdsCloseDtb(dbHandle); }/* end of loop through database files */ if (*numValidXltNty == 0) { for (dbIndex = 0; xltNtyDbList[dbIndex] != (char *)NULL; dbIndex++) { free(xltNtyDbList[dbIndex]); } free(xltNtyDbList); free(record); for (i = 0; i < numNtyKeyMatches; i++) { free(ntyKeyList[i]); } free(ntyKeyList); return EXIT_SUCCESS; } /*-------------------------------------------------------------------------- ** Sort the xltNtyList to speed up matching against data arrival. ** notifications. ** (realloc necessary because qsort needs a slot to shuffle stuff in) **------------------------------------------------------------------------*/ *xltNtyList = (NotifyArgs **) realloc(*xltNtyList,((*numValidXltNty + 1) * sizeof(NotifyArgs *))); if (*xltNtyList == (NotifyArgs **) NULL) { PANIC; } /*-------------------------------------------------------------------------- ** Sort by translated key. **------------------------------------------------------------------------*/ qsort((char *) *xltNtyList, *numValidXltNty, sizeof(NotifyArgs *), compareXltNotifications); /*-------------------------------------------------------------------------- ** Free xltNtyDbList, record, and ntyKeyList. **------------------------------------------------------------------------*/ for (dbIndex = 0; xltNtyDbList[dbIndex] != (char *)NULL; dbIndex++) { free(xltNtyDbList[dbIndex]); } free(xltNtyDbList); for (i = 0; i < numNtyKeyMatches; i++) { free(ntyKeyList[i]); } free(ntyKeyList); free(record); return EXIT_SUCCESS; } /* findXltNtyDBs() */ /*---------------------------------------------------------------------------- ** findDataNtyDBs() ** Purpose: Finds data arrival notifications whose keys exactly match the ** one of the valid translator notification keys. ** Arguments: ** startTime - Start time of the sweep in which we are interested. ** xltNtyList - List of valid translator notifications to match against. ** dataNtyList - List of matching data arrival notifications ** dataNtyKey - Regular expression of a candidate data arrival key. ** numValidXltNty - Number of translator notifications in xltNtyList. ** numValidDataNty - Number of data arrival notifications in dataNtyList. ** Returns: EXIT_SUCCESS, EXIT_FAILURE **--------------------------------------------------------------------------*/ int findDataNtyDBs(time_t startTime, NotifyArgs **xltNtyList, NotifyArgs ***dataNtyList, char *dataNtyPath, char *dataNtyKey, short numValidXltNty, short *numValidDataNty) { char *record; char **dataNtyDbList; int status, n, i; int dbIndex, keyIndex; int numNtyKeyMatches = 0; u_long dbHandle; u_long maxRecLen, oldMaxRecLen = 0; u_long maxDbKeys; PdsDataKey *tempKey; PdsDataKey **ntyKeyList; NotifyArgs *curNty; /*-------------------------------------------------------------------------- ** Make sure that the number of valid data arrival notifications ** (numValidDataNty) has been re-set to zero. **------------------------------------------------------------------------*/ *numValidDataNty = 0; /*-------------------------------------------------------------------------- ** Prime data notification database list (dataNtyDbList) and record. **------------------------------------------------------------------------*/ dataNtyDbList = (char **) calloc(1, 1); if (dataNtyDbList == (char **)NULL) { PANIC; } record = (char *) calloc(1, 1); if (record == NULL) { PANIC; } /*-------------------------------------------------------------------------- ** Find data notification databases. **------------------------------------------------------------------------*/ status = findNotifyDBs(dataNtyPath, startTime, &dataNtyDbList); if (status == 0) { PdsAdvise(Targ, PdsError, "No pool files found in Data Arrival " "notification path"); free(dataNtyDbList); free(record); return EXIT_FAILURE; } /*-------------------------------------------------------------------------- ** Search for and record valid data notifications. **------------------------------------------------------------------------*/ if (Verbose == TRUE) { PdsAdvise(Targ, PdsUp, "Search for and record valid data " "notifications.\n"); } for (dbIndex = 0; dataNtyDbList[dbIndex] != (char *)NULL; dbIndex++) { /*---------------------------------------------------------------------- ** Open a database for search. **--------------------------------------------------------------------*/ status = PdsOpenDtb(dataNtyDbList[dbIndex], &dbHandle, Readonly); if (status != 0) { PdsAdvise(Targ, PdsError, "ERROR while opening %s -- %s\n", dataNtyDbList[dbIndex], strerror(errno)); for (dbIndex = 0; dataNtyDbList[dbIndex] != (char *)NULL; dbIndex++) { free(dataNtyDbList[dbIndex]); } free(dataNtyDbList); free(record); return EXIT_FAILURE; } #ifdef DEBUG else { PdsAdvise(Targ, PdsUp, "Examining data arrival notifications " "in %s", dataNtyDbList[dbIndex]); } #endif /*---------------------------------------------------------------------- ** Get the max data record length in this database. **--------------------------------------------------------------------*/ status = MaxDataLen(dbHandle, &maxRecLen); if (status != EXIT_SUCCESS) { PdsAdvise(Targ, PdsError, "ERROR: Can't get maximum data length " "in function findDataNtyDBs"); for (n = 0; dataNtyDbList[n] != (char *)NULL; n++) { free(dataNtyDbList[n]); } free(dataNtyDbList); free(record); return EXIT_FAILURE; } /*---------------------------------------------------------------------- ** Allocate a buffer to hold the max record. **--------------------------------------------------------------------*/ if (oldMaxRecLen != maxRecLen) { record = (char *) realloc(record, (size_t)maxRecLen + 1); if (record == (char *)NULL) { PANIC; } oldMaxRecLen = maxRecLen; } /*---------------------------------------------------------------------- ** Get the number of keys in this database. **--------------------------------------------------------------------*/ status = NumDtbKeys(dbHandle, &maxDbKeys); if (status != EXIT_SUCCESS) { PdsAdvise(Targ, PdsError, "ERROR: Cound not get the number of " "data base keys in function findDataNtyDBs"); for (n = 0; dataNtyDbList[n] != (char *)NULL; n++) { free(dataNtyDbList[n]); } free(dataNtyDbList); free(record); return EXIT_FAILURE; } ntyKeyList = (PdsDataKey **)calloc(maxDbKeys, sizeof(PdsDataKey *)); if (ntyKeyList == (PdsDataKey **) NULL) { PANIC; } /*---------------------------------------------------------------------- ** Loop through the keys finding ones that are of interest. ** No sense reading & decoding data we aren't interested in. **--------------------------------------------------------------------*/ if (Verbose == TRUE) { PdsAdvise(Targ, PdsUp, "Loop through the keys finding ones that " "are of interest.\n"); } for (i = 0; i < maxDbKeys; i++) { /*---------------------------------------------------------------------- ** Get the next key **--------------------------------------------------------------------*/ if (GetKey(dbHandle, i, &tempKey) != 0) break; if (matchPdsDataKey(dataNtyKey, tempKey) == 0) ntyKeyList[numNtyKeyMatches++] = tempKey; else { PdsAdvise(Targ, PdsUp, "Match attempts: %d Matches: " "%d", i + 1, numNtyKeyMatches); } } /*---------------------------------------------------------------------- ** Loop through keys that are interesting based on their matching the ** target. Read and decode their associated data. If the frame start ** time does not match the one given on the command line or if the ** scan line is not within the specified range then eliminate the key ** from the key list -- i.e. we are not actually interested in it. **--------------------------------------------------------------------*/ if (Verbose == TRUE) { PdsAdvise(Targ, PdsUp, "Loop through keys that are interesting " "based on their matching the target.\n"); } for (keyIndex = 0; keyIndex < numNtyKeyMatches; keyIndex++) { /*------------------------------------------------------------------ ** Get the message content. **----------------------------------------------------------------*/ status = GetData(dbHandle, ntyKeyList[keyIndex], record); if (status != EXIT_SUCCESS) { PdsAdvise(Targ, PdsError, "ERROR while reading reading data " "in function GetData"); for (n = 0; dataNtyDbList[n] != (char *)NULL; n++) { free(dataNtyDbList[n]); } free(dataNtyDbList); for (n = 0; n < numNtyKeyMatches; ++n) { free(ntyKeyList[n]); } free(ntyKeyList); free(record); return EXIT_FAILURE; } curNty = (NotifyArgs *) calloc(1, sizeof(NotifyArgs)); if (curNty == (NotifyArgs *)NULL) { PANIC; } /*------------------------------------------------------------------ ** XDR decode the data arrival notification message. **----------------------------------------------------------------*/ (void)DecodeNotify(record, curNty); /*------------------------------------------------------------------ ** Confirm that this is a data arrival notification. If not try ** next. **----------------------------------------------------------------*/ if (curNty->Notify.Type != DATA_NOTIFY) { PdsAdvise(Targ, PdsError, "Config error. File %s contains " "non-data notifications.", dataNtyDbList[dbIndex]); free(curNty); continue; } /*------------------------------------------------------------------ ** Confirm that the contents _exactly_ matches one of the ** translated keys...Translator notification keys must be ** sorted. **----------------------------------------------------------------*/ status = -1; #ifdef DEBUG PdsAdvise(Targ, PdsUp, "Checking: %s", ((NotifyArgs *) curNty)->Var.Data.Tgt.key); #endif if (bsearch(curNty, xltNtyList, numValidXltNty, sizeof(NotifyArgs *), compareData2XltNotifies) == NULL) { free(curNty); continue; } /*------------------------------------------------------------------ ** This notification meets all acceptance criteria. **----------------------------------------------------------------*/ (*numValidDataNty)++; #ifdef DEBUG PdsAdvise(Targ, PdsUp, "Adding notification %02d -- %s to list...", *numValidDataNty, curNty->Var.Data.Tgt.key); #endif *dataNtyList = (NotifyArgs **) realloc(*dataNtyList, *numValidDataNty * sizeof(NotifyArgs *)); if (*dataNtyList == (NotifyArgs **)NULL) { PANIC; } (*dataNtyList)[*numValidDataNty - 1] = curNty; } /* end of for loop using numNtyKeyMatches */ /*---------------------------------------------------------------------- ** Clean up for next database. **--------------------------------------------------------------------*/ free(ntyKeyList); numNtyKeyMatches = 0; PdsCloseDtb(dbHandle); } /* end of for loop using dbIndex */ /*-------------------------------------------------------------------------- ** Free dataNtyDbList and record arrays. **------------------------------------------------------------------------*/ for (dbIndex = 0; dataNtyDbList[dbIndex] != (char *)NULL; dbIndex++) { free(dataNtyDbList[dbIndex]); } free(dataNtyDbList); free(record); if (*numValidDataNty == 0) { free(*dataNtyList); } return EXIT_SUCCESS; } /* findDataNtyDBs() */