Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

800 lines
25 KiB

  1. /*
  2. *
  3. *
  4. * Facility:
  5. *
  6. * SNMP Extension Agent
  7. *
  8. * Abstract:
  9. *
  10. * This module contains support functions for the creation and
  11. * maintenance of the in-memory cache for the HostMIB Subagent.
  12. *
  13. *
  14. * Author:
  15. *
  16. * D. D. Burns @ WebEnable, Inc.
  17. *
  18. *
  19. * Revision History:
  20. *
  21. * V1.00 - 04/17/97 D. D. Burns Original Creation
  22. *
  23. *
  24. */
  25. /*
  26. Host-MIB Cache Overview
  27. -----------------------
  28. This module contains functions that create, maintain and allow for searching
  29. data structures that are used to implement a cache of HOST-MIB information.
  30. Typically, a cache is created on a "per-table" basis and is formed as a
  31. linked-list of CACHEROW structures (all cache structures are defined in
  32. "HMCACHE.H"), one CACHEROW structure for each logical "row" in the table.
  33. The list-head of each cache is a CACHEHEAD structure and is instantiated in
  34. the source module for the functions that service the attributes in that
  35. table (so the CACHEHEAD structure for the cache for the "hrStorage" table is
  36. in module "HRSTOENT.C").
  37. Caches are created at start-up time by special cache-creation functions (coded
  38. to the specs for each table) in each of the "table" source modules. Those
  39. cache-creation functions (plus the associated "get" and "set" functions) use
  40. the general cache manipulation functions in this module.
  41. For example, a typical cache looks like this:
  42. HrStorage Table Cache
  43. "hrStorage_cache"
  44. (statically allocated
  45. in "HRSTOENT.C")...
  46. *============*
  47. | CACHEHEAD |
  48. | "list"....|--* ..(Malloced as a single instance in function
  49. *============* | . "CreateTableRow")
  50. V .
  51. *================* ..(Malloced as an
  52. | CACHEROW | . array in function
  53. *--|...."next" | . "CreateTableRow()")
  54. | | "index".....|--> "1" .
  55. | | "attrib_list"..|--> *===============*
  56. | *================* | ATTRIB |
  57. | | "attrib_type".|-->CA_NUMBER
  58. | | "u.unumber"...|-->"4"
  59. | +---------------+
  60. | | ATTRIB |
  61. | | "attrib_type".|-->CA_STRING
  62. | | "u.string"....|-->"<string>"
  63. | +---------------+
  64. | | ATTRIB |
  65. | | "attrib_type".|-->CA_CACHE
  66. | | "u.cache"...|------*
  67. | +---------------+ |
  68. | . |
  69. | *=================* . |
  70. *->| CACHEROW | . |
  71. *--|...."next" | *============*
  72. | | "index"......|-->"2" | CACHEHEAD |
  73. | | "attrib_list"..|--> | "list"....|--*
  74. | *=================* *============* |
  75. V (For doubly |
  76. indexed V
  77. tables)
  78. The general cache manipulation functions in this module include:
  79. Name Purpose
  80. ---- -------
  81. CreateTableRow Creates an instance of a CACHEROW with a given
  82. attribute count. (This function does not link the
  83. instance into any list, it merely mallocs storage).
  84. AddTableRow Given an index value, a CACHEROW instance (created by
  85. "CreateTableRow()" above) and a CACHEHEAD, this
  86. function links the CACHEROW instance into the list
  87. described by CACHEHEAD in the proper place given the
  88. index value.
  89. These two functions above are used to populate the cache
  90. (typically at startup time).
  91. Name Purpose
  92. ---- -------
  93. FindTableRow Given an index value and a CACHEHEAD, this function
  94. returns a pointer to the CACHEROW instance in the
  95. CACHEHEAD cache that has the given index. This
  96. function is used to find a given cache entry (ie table
  97. "row") in service for a "get" or "set" routine.
  98. FindNextTableRow Given an index value and a CACHEHEAD, this function
  99. returns a pointer to the CACHEROW instance in the
  100. CACHEHEAD cache that IMMEDIATELY FOLLOWS the given
  101. index. This function is used to find a given cache
  102. entry (ie table "row") in service for "get-next"
  103. situations.
  104. GetNextTableRow Given a CACHEROW row (obtained using either of the
  105. routines above), this gets the next entry regardless
  106. of index (or NULL if the given row is the last row).
  107. (Implemented as a macro in "HMCACHE.H").
  108. RemoveTableRow Given an index value and a CACHEHEAD, this function
  109. unlinks the CACHEROW instance from the cache list
  110. described by CACHEHEAD. (TBD in support of PNP)
  111. DestroyTable Given a pointer to a CACHEHEAD, this function releases
  112. every row-instance in the cache (through calls to
  113. "DestroyTableRow()" below). This function presumes
  114. that the CACHEHEAD itself is statically allocated.
  115. DestroyTableRow Given an instance of an (unlinked) CACHEROW, this
  116. function releases the storage associated with it.
  117. For Debugging:
  118. Name Purpose
  119. ---- -------
  120. PrintCache Prints a dump on an output file in ASCII of the
  121. contents of a specified cache. Only works for caches
  122. for which a "print-row" function is defined and
  123. referenced in the CACHEHEAD structure for the cache.
  124. */
  125. /*
  126. | INCLUDES:
  127. */
  128. #include <windows.h>
  129. #include <stdlib.h>
  130. #include <stdio.h> /* for debug printf */
  131. #include <time.h> /* for debug support */
  132. #include <malloc.h>
  133. #include "hmcache.h"
  134. /*
  135. |==============================================================================
  136. | Debug File Channel
  137. |
  138. */
  139. #if defined(CACHE_DUMP) || defined(PROC_CACHE)
  140. FILE *Ofile;
  141. #endif
  142. /* CreateTableRow - Create a CACHEROW structure for attributes in a table */
  143. /* CreateTableRow - Create a CACHEROW structure for attributes in a table */
  144. /* CreateTableRow - Create a CACHEROW structure for attributes in a table */
  145. CACHEROW *
  146. CreateTableRow(
  147. ULONG attribute_count
  148. )
  149. /*
  150. | EXPLICIT INPUTS:
  151. |
  152. | "attribute_count" indicates how much storage to allocate for the
  153. | array of attributes for this row.
  154. |
  155. | IMPLICIT INPUTS:
  156. |
  157. | None.
  158. |
  159. | OUTPUTS:
  160. |
  161. | On Success:
  162. | Function returns a pointer to allocated storage containing an
  163. | image of a CACHEROW structure. Enough storage for each attribute
  164. | in the row has been allocated within array "attrib_list[]" and the
  165. | count of these elements has been stored in the CACHEROW structure.
  166. |
  167. | On any Failure:
  168. | Function returns NULL (indicating "not enough storage").
  169. |
  170. | THE BIG PICTURE:
  171. |
  172. | At subagent startup time, the caches for each table in the MIB is
  173. | populated with rows for each row in the table. This function is
  174. | invoked by the start-up code for each table to create one CACHEROW
  175. | structure for each row needed in the table.
  176. |
  177. | With the advent of PNP, this function can be called after startup
  178. | time to add rows to an existing table.
  179. |
  180. | (Note: The actual insertion of an instance returned by this function
  181. | into a cache table list is done by function "AddTableRow()" ).
  182. |
  183. | OTHER THINGS TO KNOW:
  184. |
  185. | "DestroyTableRow()" deallocates storage associated with any instance
  186. | of a CACHEROW structure created by this function.
  187. |
  188. */
  189. {
  190. CACHEROW *new=NULL; /* New CACHEROW instance to be created */
  191. ULONG i; /* handy-dandy Index */
  192. /* Create the main CACHEROW structure to be returned . . . */
  193. if ( (new = (CACHEROW *) malloc(sizeof(CACHEROW))) == NULL) {
  194. /* "Not Enough Storage" */
  195. return (NULL);
  196. }
  197. /*
  198. | Now try to allocate enough storage for the array of attributes in this row
  199. */
  200. if ( (new->attrib_list = (ATTRIB *) malloc(sizeof(ATTRIB) * attribute_count))
  201. == NULL) {
  202. /* "Not Enough Storage" */
  203. free( new ) ; /* Blow off the CACHEROW we won't be returning */
  204. return (NULL);
  205. }
  206. /* Indicate how big this array is so DestroyTableRow() can do the right thing*/
  207. new->attrib_count = attribute_count;
  208. /* Zap each array entry so things are clean */
  209. for (i = 0; i < attribute_count; i += 1) {
  210. new->attrib_list[i].attrib_type = CA_UNKNOWN;
  211. new->attrib_list[i].u.string_value = NULL;
  212. }
  213. new->index = 0; /* No legal index yet */
  214. new->next = NULL; /* Not in the cache list yet */
  215. /* Return the newly allocated CACHEROW structure for further population */
  216. return ( new ) ;
  217. }
  218. /* DestroyTable - Destroy all rows in CACHEHEAD structure (Release Storage) */
  219. /* DestroyTable - Destroy all rows in CACHEHEAD structure (Release Storage) */
  220. /* DestroyTable - Destroy all rows in CACHEHEAD structure (Release Storage) */
  221. void
  222. DestroyTable(
  223. CACHEHEAD *cache /* Cache whose rows are to be Released */
  224. )
  225. /*
  226. | EXPLICIT INPUTS:
  227. |
  228. | "cache" is the CACHEHEAD instance of a table for which all rows are
  229. | to be released.
  230. |
  231. | IMPLICIT INPUTS:
  232. |
  233. | None.
  234. |
  235. | OUTPUTS:
  236. |
  237. | On Success/Failure:
  238. | Function returns, the CACHEHEAD is set to reflect an empty cache.
  239. |
  240. | THE BIG PICTURE:
  241. |
  242. | At subagent startup time, the caches for each table in the MIB is
  243. | populated with rows for each row in the table. "CreateTableRow" is
  244. | invoked by the start-up code for each table to create one CACHEROW
  245. | structure for each row needed in the table.
  246. |
  247. | With the advent of PNP, this function can be called after startup
  248. | time to delete all storage associated with such a cache.
  249. |
  250. |
  251. | OTHER THINGS TO KNOW:
  252. |
  253. | This function may be recursively invoked through the call to
  254. | "DestroyTable()" inside "DestroyTableRow()".
  255. |
  256. | This function may be safely invoked on an "empty" cache-head.
  257. |
  258. | This function doesn't attempt to release storage associated with
  259. | the CACHEHEAD structure itself.
  260. |
  261. | DOUBLE NOTE:
  262. | This function simply releases storage. You can't go calling this
  263. | function willy-nilly on just any cache without taking into
  264. | consideration the semantics of what maybe being blown away. For
  265. | instance, some tables in Host MIB contain attributes whose values
  266. | are indices into other tables. If a table is destroyed and rebuilt,
  267. | clearly the references to the rebuilt table must be refreshed in
  268. | some manner.
  269. */
  270. {
  271. /*
  272. | If an old copy of the cache exists, blow it away now
  273. */
  274. while (cache->list != NULL) {
  275. CACHEROW *row_to_go;
  276. /* Pick up the row to blow away */
  277. row_to_go = cache->list;
  278. /* Change the cache-head to point to the next row (if any) */
  279. cache->list = GetNextTableRow(row_to_go);
  280. DestroyTableRow(row_to_go);
  281. }
  282. /* Show no entries in the cache */
  283. cache->list_count = 0;
  284. }
  285. /* DestroyTableRow - Destroy a CACHEROW structure (Release Storage) */
  286. /* DestroyTableRow - Destroy a CACHEROW structure (Release Storage) */
  287. /* DestroyTableRow - Destroy a CACHEROW structure (Release Storage) */
  288. void
  289. DestroyTableRow(
  290. CACHEROW *row /* Row to be Released */
  291. )
  292. /*
  293. | EXPLICIT INPUTS:
  294. |
  295. | "row" is the instance of a row to be released.
  296. |
  297. | IMPLICIT INPUTS:
  298. |
  299. | None.
  300. |
  301. | OUTPUTS:
  302. |
  303. | On Success/Failure:
  304. | Function returns.
  305. |
  306. | THE BIG PICTURE:
  307. |
  308. | At subagent startup time, the caches for each table in the MIB is
  309. | populated with rows for each row in the table. "CreateTableRow" is
  310. | invoked by the start-up code for each table to create one CACHEROW
  311. | structure for each row needed in the table.
  312. |
  313. | With the advent of PNP, this function can be called after startup
  314. | time to delete storage associated with rows being replaced in an
  315. | existing table.
  316. |
  317. | (Note: The actual deletion of an row instance from a cache table list
  318. | must be done before this function is called).
  319. |
  320. | OTHER THINGS TO KNOW:
  321. |
  322. | "CreateTableRow()" creates an instance of what this function
  323. | "destroys".
  324. |
  325. | This function may be recursively invoked through the call to
  326. | "DestroyTable()" in the event we are release a row that contains
  327. | an attribute "value" that is really another table (in the case
  328. | of a multiply-indexed attribute).
  329. |
  330. | DOUBLE NOTE:
  331. | This function simply releases storage. You can't go calling this
  332. | function willy-nilly on just any cache without taking into
  333. | consideration the semantics of what maybe being blown away. For
  334. | instance, some tables in Host MIB contain attributes whose values
  335. | are indices into other tables. If a row is destroyed, clearly the
  336. | references to the row must be refreshed in some manner.
  337. */
  338. {
  339. CACHEROW *new=NULL; /* New CACHEROW instance to be created */
  340. ULONG i; /* handy-dandy Index */
  341. /* Zap storage associated each attribute entry (if any) */
  342. for (i = 0; i < row->attrib_count; i += 1) {
  343. /* Blow off storage for attribute values that have malloc-ed storage */
  344. switch (row->attrib_list[i].attrib_type) {
  345. case CA_STRING:
  346. free( row->attrib_list[i].u.string_value );
  347. break;
  348. case CA_CACHE:
  349. /* Release the contents of the entire cache */
  350. DestroyTable( row->attrib_list[i].u.cache );
  351. /* Free the storage containing the cache
  352. free ( row->attrib_list[i].u.cache );
  353. break;
  354. case CA_NUMBER:
  355. case CA_COMPUTED:
  356. case CA_UNKNOWN:
  357. /* No malloced storage associated with these types */
  358. default:
  359. break;
  360. }
  361. }
  362. /* Free the storage associated with the array of attributes */
  363. free( row->attrib_list);
  364. /* Free the storage for the row itself */
  365. free( row );
  366. }
  367. /* AddTableRow - Adds a specific "row" into a cached "table" */
  368. /* AddTableRow - Adds a specific "row" into a cached "table" */
  369. /* AddTableRow - Adds a specific "row" into a cached "table" */
  370. BOOL
  371. AddTableRow(
  372. ULONG index, /* Index for row desired */
  373. CACHEROW *row, /* Row to be added to .. */
  374. CACHEHEAD *cache /* this cache */
  375. )
  376. /*
  377. | EXPLICIT INPUTS:
  378. |
  379. | "index" is index inserted into "row" before the
  380. | "row" is added to "cache".
  381. |
  382. | IMPLICIT INPUTS:
  383. |
  384. | None.
  385. |
  386. | OUTPUTS:
  387. |
  388. | On Success:
  389. | Function returns TRUE indicating that the row was successfully
  390. | added to the cache for the table.
  391. |
  392. | On Failure:
  393. | Function returns FALSE, indicating that the row already
  394. | existed.
  395. |
  396. | THE BIG PICTURE:
  397. |
  398. | At startup time the subagent is busy populating the cache for
  399. | each table. The rows in any cached table are inserted into
  400. | the cache by this function.
  401. |
  402. | OTHER THINGS TO KNOW:
  403. |
  404. | Code in this function presumes that the list (cache) is in sorted index
  405. | order.
  406. |
  407. | Any change of organization of the linked list that constitutes the
  408. | cache will impact this function and "Find(Next)TableRow()".
  409. |
  410. */
  411. {
  412. CACHEROW **index_row; /* Used for searching cache */
  413. /* NOTE: It always points at a cell that */
  414. /* points to the next list element */
  415. /* (if any is on the list). */
  416. /* Whip down the list until there is no "next" or "next" is "bigger" . . . */
  417. for ( index_row = &cache->list;
  418. *index_row != NULL;
  419. index_row = &((*index_row)->next)
  420. ) {
  421. /* If this row MATCHES the to-be-inserted row: Error! */
  422. if ((*index_row)->index == index) {
  423. return ( FALSE );
  424. }
  425. /*
  426. | If next cache entry is "Greater Than" new index, then
  427. | "index_row" points to the cell that should be changed to insert
  428. | the new entry.
  429. */
  430. if ((*index_row)->index > index) {
  431. break;
  432. }
  433. /* Otherwise we should try for a "next" entry in the list */
  434. }
  435. /*
  436. | When we fall thru here "index_row" contains the address of the cell to
  437. | change to add the new row into the cache (might be in the list-head,
  438. | might be in a list-entry)
  439. */
  440. row->next = *index_row; /* Put cache-list "next" into new row element */
  441. *index_row = row; /* Insert new row into the list */
  442. row->index = index; /* Stick the index into the row entry itself */
  443. cache->list_count += 1; /* Count another entry on the cache list */
  444. /* Successful insertion */
  445. return (TRUE);
  446. }
  447. /* FindTableRow - Finds a specific "row" in a cached "table" */
  448. /* FindTableRow - Finds a specific "row" in a cached "table" */
  449. /* FindTableRow - Finds a specific "row" in a cached "table" */
  450. CACHEROW *
  451. FindTableRow(
  452. ULONG index, /* Index for row desired */
  453. CACHEHEAD *cache /* Table cache to search */
  454. )
  455. /*
  456. | EXPLICIT INPUTS:
  457. |
  458. | "index" indicates which table row entry is desired
  459. | "cache" indicates the cache list to search for the desired row.
  460. |
  461. | IMPLICIT INPUTS:
  462. |
  463. | None.
  464. |
  465. | OUTPUTS:
  466. |
  467. | On Success:
  468. | Function returns a pointer to the instance of a CACHEROW structure
  469. | for the desired row.
  470. |
  471. | On any Failure:
  472. | Function returns NULL (indicating "no such entry" or "cache empty").
  473. |
  474. | THE BIG PICTURE:
  475. |
  476. | As the subagent runs, the "get" functions for the attributes that do
  477. | not "compute" their values dynamically must lookup cached values.
  478. |
  479. | This function can be used by any "get" function that knows the
  480. | CACHEHEAD for it's table to find a specific row containing an
  481. | attribute value to be returned.
  482. |
  483. | OTHER THINGS TO KNOW:
  484. |
  485. | Code in this function presumes that the list is in sorted index
  486. | order (hence it gives up once encountering an entry whose index
  487. | is "too big").
  488. |
  489. | Any change of organization of the linked list that constitutes the
  490. | cache will impact this function and "AddTableRow()".
  491. |
  492. */
  493. {
  494. CACHEROW *row=NULL; /* Row instance to be returned, initially none */
  495. /* Whip down the list until there is no next . . */
  496. for ( row = cache->list; row != NULL; row = row->next ) {
  497. /* If this is "It": Return it */
  498. if (row->index == index) {
  499. return ( row );
  500. }
  501. /* If this is "Greater Than IT", it's not in the list: Return NULL */
  502. if (row->index > index) {
  503. return ( NULL );
  504. }
  505. /* Otherwise we should try for a "next" entry in the list */
  506. }
  507. /*
  508. | If we fall thru here we didn't find the desired entry because the cache
  509. | list is either empty or devoid of the desired row.
  510. */
  511. return (NULL);
  512. }
  513. /* FindNextTableRow - Finds Next row after a given "index" in a cache */
  514. /* FindNextTableRow - Finds Next row after a given "index" in a cache */
  515. /* FindNextTableRow - Finds Next row after a given "index" in a cache */
  516. CACHEROW *
  517. FindNextTableRow(
  518. ULONG index, /* Index for row desired */
  519. CACHEHEAD *cache /* Table cache to search */
  520. )
  521. /*
  522. | EXPLICIT INPUTS:
  523. |
  524. | "index" indicates which table row entry AFTER WHICH the NEXT is
  525. | desired. The "index" row need not exist (could be before
  526. | the first row or a missing row "in the middle", but
  527. | it may not specify a row that would be after the
  528. | last in the table.
  529. |
  530. | "cache" indicates the cache list to search for the desired row.
  531. |
  532. | IMPLICIT INPUTS:
  533. |
  534. | None.
  535. |
  536. | OUTPUTS:
  537. |
  538. | On Success:
  539. | Function returns a pointer to the instance of a CACHEROW structure
  540. | for the desired NEXT row.
  541. |
  542. | On any Failure:
  543. | Function returns NULL (indicating "no such entry", "cache empty" or
  544. | "end-of-cache" reached).
  545. |
  546. | THE BIG PICTURE:
  547. |
  548. | As the subagent runs, the "get-next" functions for the attributes
  549. | that do not "compute" their values dynamically must lookup cached
  550. | values.
  551. |
  552. | This function can be used by any "FindNextInstance" function that
  553. | knows the CACHEHEAD for it's table to find the NEXT row following a
  554. | specific row containing an attribute value to be returned.
  555. |
  556. | OTHER THINGS TO KNOW:
  557. |
  558. | To get the first entry in a table, supply an ("illegal") index of 0.
  559. |
  560. | Any change of organization of the linked list that constitutes the
  561. | cache will impact this function, "FindTableRow()" and "AddTableRow()".
  562. |
  563. */
  564. {
  565. CACHEROW *row=NULL; /* Row instance to be returned, initially none */
  566. /*
  567. | If there is a non-empty cache and the input "index" is less than
  568. | the first entry in the cache, simply return the first entry.
  569. */
  570. if ( cache->list != NULL /* If there is a non-empty cache . . . */
  571. && index < cache->list->index /* AND index is LESS THAN first entry */
  572. ) {
  573. /* Return the first entry in the table */
  574. return (cache->list);
  575. }
  576. /* Whip down the list until there is no next . . */
  577. for ( row = cache->list; row != NULL; row = row->next ) {
  578. /* If "index" specifies THIS ROW . . . */
  579. if (row->index == index) {
  580. return ( row->next ); /* Return NEXT (or NULL if no "next") */
  581. }
  582. /* If this is "Greater Than IT", "index" is not in the list */
  583. if (row->index > index) {
  584. return ( row ); /* Return CURRENT, it is Greater than "index"*/
  585. }
  586. /* Otherwise we should try for a "next" entry in the list */
  587. }
  588. /*
  589. | If we fall thru here then the cache is empty or the "index" specifies
  590. | a row after the last legal row.
  591. */
  592. return (NULL);
  593. }
  594. #if defined(CACHE_DUMP)
  595. /* PrintCache - Dumps for debugging the contents of a cache */
  596. /* PrintCache - Dumps for debugging the contents of a cache */
  597. /* PrintCache - Dumps for debugging the contents of a cache */
  598. void
  599. PrintCache(
  600. CACHEHEAD *cache /* Table cache to dump */
  601. )
  602. /*
  603. | EXPLICIT INPUTS:
  604. |
  605. | "cache" indicates the cache whose contents is to be dumped.
  606. |
  607. | IMPLICIT INPUTS:
  608. |
  609. | None.
  610. |
  611. | OUTPUTS:
  612. |
  613. | On Success:
  614. | Function returns. It may be called recursively by a "Print-Row"
  615. | function.
  616. |
  617. |
  618. | THE BIG PICTURE:
  619. |
  620. | For debugging only.
  621. |
  622. | OTHER THINGS TO KNOW:
  623. |
  624. | Define "CACHE_DUMP" at the top of "HMCACHE.H" to enable this debug
  625. | support. You can change the file into which output goes by modifying
  626. | "DUMP_FILE", also in "HMCACHE.H".
  627. |
  628. */
  629. #define DO_CLOSE \
  630. { if ((open_count -= 1) == 0) { fclose(OFILE); } }
  631. {
  632. CACHEROW *row; /* Row instance to dump. */
  633. UINT i; /* Element counter */
  634. time_t ltime; /* For debug message */
  635. static
  636. UINT open_count=0; /* We can be called recursively */
  637. /* Avoid a recursive open */
  638. if (open_count == 0) {
  639. /* Open the debug log file */
  640. if ((Ofile=fopen(DUMP_FILE, "a+")) == NULL) {
  641. return;
  642. }
  643. /*
  644. | Put a time stamp into the debug file because we're opening for append.
  645. */
  646. time( &ltime);
  647. fprintf(OFILE, "=============== Open for appending: %s\n", ctime( &ltime ));
  648. }
  649. open_count += 1;
  650. if (cache == NULL) {
  651. fprintf(OFILE, "Call to PrintCache with NULL CACHEHEAD pointer.\n");
  652. DO_CLOSE;
  653. return;
  654. }
  655. if (cache->print_row == NULL) {
  656. fprintf(OFILE,
  657. "Call to PrintCache with NULL CACHEHEAD Print-Routine pointer.\n");
  658. DO_CLOSE;
  659. return;
  660. }
  661. /* Print a Title */
  662. cache->print_row(NULL);
  663. fprintf(OFILE, "Element Count: %d\n", cache->list_count);
  664. /* For every row in the cache . . . */
  665. for (row = cache->list, i = 0; row != NULL; row = row->next, i += 1) {
  666. fprintf(OFILE, "\nElement #%d, Internal Index %d, at 0x%x:\n",
  667. i, row->index, row);
  668. cache->print_row(row);
  669. }
  670. fprintf(OFILE, "======== End of Cache ========\n\n");
  671. DO_CLOSE;
  672. }
  673. #endif // defined(CACHE_DUMP)