root/branches/stable/src/client/usrint/ucache.c @ 9266

Revision 9266, 60.7 KB (checked in by walt, 15 months ago)

minor change

Line 
1/*
2 * (C) 2011 Clemson University
3 *
4 * See COPYING in top-level directory.
5 */
6
7/**
8 * \file 
9 * \ingroup usrint
10 *
11 * Experimental cache for user data.
12 *
13 */
14#include "usrint.h"
15#include "posix-ops.h"
16#include "openfile-util.h"
17#include "iocommon.h"
18#if PVFS_UCACHE_ENABLE
19#include "ucache.h"
20#include <gen-locks.h>
21
22/* Global Variables */
23FILE *out;                   /* For Logging Purposes */
24
25/* static uint32_t ucache_blk_cnt = 0; */
26
27/* Global pointers to data in shared mem. Pointers set in ucache_initialize */
28union user_cache_u *ucache = 0;
29struct ucache_aux_s *ucache_aux = 0; /* All locks and stats stored here */
30
31/* ucache_aux is a pointer to the actual data summarized by the following
32 * pointers
33*/
34ucache_lock_t *ucache_locks = 0; /* The shmem of all ucache locks */
35ucache_lock_t *ucache_lock = 0;  /* Global Lock maintaining concurrency */
36struct ucache_stats_s *ucache_stats = 0; /* Pointer to stats structure*/
37
38/* Per-process (thread) execution statistics */
39struct ucache_stats_s these_stats = { 0, 0, 0, 0, 0 };
40
41/* Flags indicating ucache status */
42int ucache_enabled = 0;
43char ftblInitialized = 0;
44
45/* Internal Only Function Declarations */
46
47/* Initialization */
48static void add_mtbls(uint16_t blk);
49static void init_memory_table(struct mem_table_s *mtbl);
50static inline int init_memory_entry(struct mem_table_s *mtbl, int16_t index);
51
52/* Gets */
53static uint16_t get_next_free_mtbl(uint16_t *free_mtbl_blk, uint16_t *free_mtbl_ent);
54static uint16_t get_free_fent(void);
55static inline uint16_t get_free_ment(struct mem_table_s *mtbl);
56static inline uint16_t get_free_blk(void);
57
58/* Puts */
59static int put_free_mtbl(struct mem_table_s *mtbl, struct file_ent_s *file);
60static void put_free_fent(struct file_ent_s *fent);
61static void put_free_ment(struct mem_table_s *mtbl, uint16_t ent);
62static inline void put_free_blk(uint16_t blk);
63
64/* File Entry Chain Iterator */
65static unsigned char file_done(uint16_t index);
66static uint16_t file_next(struct file_table_s *ftbl, uint16_t index);
67
68/* Memory Entry Chain Iterator */
69static inline unsigned char ment_done(uint16_t index);
70static inline uint16_t ment_next(struct mem_table_s *mtbl, uint16_t index);
71
72/* Dirty List Iterator */
73static inline unsigned char dirty_done(uint16_t index);
74static inline uint16_t dirty_next(struct mem_table_s *mtbl, uint16_t index);
75
76/* File and Memory Insertion */
77uint16_t insert_file(uint32_t fs_id, uint64_t handle);
78
79static inline void *insert_mem(struct file_ent_s *fent,
80                                       uint64_t offset,
81                                    uint16_t *block_ndx
82);
83
84static inline void *set_item(struct file_ent_s *fent,
85                      uint64_t offset,
86                      uint16_t index
87);
88
89/* File and Memory Lookup */
90static struct mem_table_s *lookup_file(
91    uint32_t fs_id,
92    uint64_t handle,
93    uint16_t *file_mtbl_blk,    /* Can be NULL if not desired */
94    uint16_t *file_mtbl_ent, 
95    uint16_t *file_ent_index,
96    uint16_t *file_ent_prev_index
97);
98static inline void *lookup_mem(struct mem_table_s *mtbl,
99                    uint64_t offset,
100                    uint16_t *item_index,
101                    uint16_t *mem_ent_index,
102                    uint16_t *mem_ent_prev_index
103);
104
105/* File and Memory Entry Removal */
106static int remove_file(struct file_ent_s *fent);
107static int wipe_mtbl(struct mem_table_s *mtbl);
108static int remove_mem(struct file_ent_s *fent, uint64_t offset);
109
110/* Eviction Utilities */
111static uint16_t locate_max_fent(struct file_ent_s **fent);
112static void update_LRU(struct mem_table_s *mtbl, uint16_t index);
113static int evict_LRU(struct file_ent_s *fent);
114
115/* Logging */
116//static void log_ucache_stats(void);
117
118/* List Printing Functions */
119void print_LRU(struct mem_table_s *mtbl);
120void print_dirty(struct mem_table_s *mtbl);
121
122/* Flushing of individual files and blocks */
123int flush_file(struct file_ent_s *fent);
124int flush_block(struct file_ent_s *fent, struct mem_ent_s *ment);
125
126/*  Externally Visible API
127 *      The following functions are thread/processor safe regarding the cache
128 *      tables and data.     
129 */
130
131/** 
132 * Initializes the cache.
133 * Mainly, it aquires a previously created shared memory segment used to
134 * cache data. The shared mem. creation and ftbl initialization should already
135 * have been done by the daemon at this point.
136 *
137 * The whole cache is protected globally by a locking mechanism.
138 *
139 * Locks (same type as global lock) can be used to protect block level data.
140 */
141int ucache_initialize(void)
142{
143    int rc = 0;
144    //gossip_set_debug_mask(1, GOSSIP_UCACHE_DEBUG); 
145
146    /* Aquire pointers to shmem segments (ucache_aux and ucache) */
147    /* shmget segment containing ucache_aux */
148    key_t key = ftok(KEY_FILE, SHM_ID1);
149    int shmflg = SVSHM_MODE;
150    int aux_shmid = shmget(key, 0, shmflg);
151    if(aux_shmid == -1)
152    {
153        //gossip_debug(GOSSIP_UCACHE_DEBUG,
154        //    "ucache_initialize - ucache_aux shmget: errno = %d\n", errno);
155        return -1;
156    }
157    /* shmat ucache_aux */
158    ucache_aux = shmat(aux_shmid, NULL, 0);
159    if((long int)ucache_aux == -1)
160    {
161        //gossip_debug(GOSSIP_UCACHE_DEBUG,       
162        //    "ucache_initialize - ucache_aux shmat: errno = %d\n", errno);
163        return -1;
164    }
165
166    /* Set our global pointers to data in the ucache_aux struct */
167    ucache_locks = ucache_aux->ucache_locks;
168    ucache_lock = get_lock(BLOCKS_IN_CACHE);
169    ucache_stats = &(ucache_aux->ucache_stats);
170
171    /* ucache */
172    key = ftok(KEY_FILE, SHM_ID2);
173    int ucache_shmid = shmget(key, 0, shmflg);
174    if(ucache_shmid == -1)
175    {
176        //gossip_debug(GOSSIP_UCACHE_DEBUG,       
177        //    "ucache_initialize - ucache shmget: errno = %d\n", errno);
178        return -1;
179    }
180    ucache = (union user_cache_u *)shmat(ucache_shmid, NULL, 0);
181    if((long int)ucache == -1)
182    {
183        //gossip_debug(GOSSIP_UCACHE_DEBUG,       
184        //    "ucache_initialize - ucache shmat: errno = %d\n", errno);
185        return -1;
186    }
187
188    /* When this process ends we may want to dump ucache stats to a log file */
189    //rc = atexit(log_ucache_stats);   
190
191    /* Declare the ucache enabled! */
192    ucache_enabled = 1;
193    return rc;
194}
195
196/**
197 * Returns a pointer to the mtbl corresponding to the blk & ent.
198 * Input must be reliable otherwise invalid mtbl could be returned.
199 */
200inline struct mem_table_s *get_mtbl(uint16_t mtbl_blk, uint16_t mtbl_ent)
201{
202    if( mtbl_blk < BLOCKS_IN_CACHE &&
203        mtbl_ent < MEM_TABLE_ENTRY_COUNT)
204    {
205        return &(ucache->b[mtbl_blk].mtbl[mtbl_ent]);
206    }
207    else
208    {
209        return (struct mem_table_s *)NILP;
210    }
211}
212
213/**
214 * Initializes the ucache file table if it hasn't previously been initialized.
215 * Although this function is visible, DO NOT CALL THIS FUNCTION.
216 * It is meant to be called in the ucache daemon or during testing.
217 * see: src/apps/ucache/ucached.c for more info.
218 *
219 * Sets the char booelan ftblInitialized when ftbl has been successfully
220 * initialized.
221 *
222 * Returns 0 on success, -1 on failure.
223 */
224int ucache_init_file_table(char forceCreation)
225{
226    int i;
227
228    /* check if already initialized? */
229    if(ftblInitialized == 1 && !forceCreation)
230    {
231        return -1;
232    }
233    if(ucache)
234    {
235        memset(ucache, 0, CACHE_SIZE);
236    }
237    else
238    {
239        return -1;
240    }
241       
242
243    /* initialize mtbl free list table */
244    ucache->ftbl.free_mtbl_blk = NIL16;
245    ucache->ftbl.free_mtbl_ent = NIL16;
246    add_mtbls(0);
247
248    /* set up list of free blocks */
249    ucache->ftbl.free_blk = 1;
250    for (i = 1; i < (BLOCKS_IN_CACHE - 1); i++)
251    {
252        ucache->b[i].mtbl[0].free_list_blk = i + 1;
253    }
254    ucache->b[BLOCKS_IN_CACHE - 1].mtbl[0].free_list_blk = NIL16;
255
256    /* set up file hash table */
257    for (i = 0; i < FILE_TABLE_HASH_MAX; i++)
258    {
259        ucache->ftbl.file[i].tag_handle = NIL64;
260        ucache->ftbl.file[i].tag_id = NIL32;
261        ucache->ftbl.file[i].mtbl_blk = NIL16;
262        ucache->ftbl.file[i].mtbl_ent = NIL16;
263        ucache->ftbl.file[i].next = NIL16;
264    }
265
266    /* set up list of free hash table entries */
267    ucache->ftbl.free_list = FILE_TABLE_HASH_MAX;
268    for (i = FILE_TABLE_HASH_MAX; i < FILE_TABLE_ENTRY_COUNT - 1; i++)
269    {
270        ucache->ftbl.file[i].mtbl_blk = NIL16;
271        ucache->ftbl.file[i].mtbl_ent = NIL16;
272        ucache->ftbl.file[i].next = i + 1;
273    }
274    ucache->ftbl.file[FILE_TABLE_ENTRY_COUNT - 1].next = NIL16;
275
276    /* Success */
277    ftblInitialized = 1;
278    return 0;
279}
280
281/**
282 * Opens a file in ucache.
283 */
284int ucache_open_file(PVFS_fs_id *fs_id,
285                     PVFS_handle *handle,
286                     struct file_ent_s **fent)
287{
288    int rc = -1;
289    uint16_t file_mtbl_blk;
290    uint16_t file_mtbl_ent;
291    uint16_t file_ent_index;
292    uint16_t file_ent_prev_index;
293
294    lock_lock(ucache_lock);
295
296    struct mem_table_s *mtbl = lookup_file((uint32_t)(*fs_id),
297                                           (uint64_t)(*handle),
298                                            &file_mtbl_blk,
299                                            &file_mtbl_ent,
300                                            &file_ent_index,
301                                            &file_ent_prev_index);
302
303    if(mtbl == (struct mem_table_s *)NIL)
304    {
305        uint16_t fentIndex  = insert_file((uint32_t)*fs_id, (uint64_t)*handle);
306        if(fentIndex > FILE_TABLE_ENTRY_COUNT)
307        {
308            rc = -1;
309            goto done;
310        }
311        *fent = &(ucache->ftbl.file[fentIndex]);
312        if((*fent)->mtbl_blk == NIL16 || (*fent)->mtbl_ent == NIL16)
313        {
314            rc = -1;
315            goto done;
316        }
317
318        mtbl = get_mtbl((*fent)->mtbl_blk, (*fent)->mtbl_ent);
319        if(mtbl == (struct mem_table_s *)NILP)
320        {   
321            /* Error - Could not insert */
322            rc = -1;
323            goto done;
324        }
325        else
326        {
327            /* File Inserted */
328            mtbl->ref_cnt = 1;
329            rc = 0;
330            goto done;
331        }
332    }
333    else
334    {
335        /* File was previously Inserted */
336        mtbl->ref_cnt++;
337        *fent = &(ucache->ftbl.file[file_ent_index]);
338        rc = 1;
339        goto done;
340    }
341done:
342    lock_unlock(ucache_lock);
343    return rc;
344}
345
346/**
347 * Returns ptr to block in ucache based on file and offset
348 */
349inline void *ucache_lookup(struct file_ent_s *fent, uint64_t offset,
350                                         uint16_t *block_ndx)
351{
352    void *retVal = (void *) NIL;
353    if(fent)
354    {
355        lock_lock(ucache_lock);
356        struct mem_table_s *mtbl = get_mtbl(fent->mtbl_blk, fent->mtbl_ent);
357        retVal = lookup_mem(mtbl,
358                            offset,
359                            block_ndx,
360                            NULL,
361                            NULL);
362        lock_unlock(ucache_lock);
363    }
364    return retVal;
365}
366
367/**
368 * Prepares the data structures for block storage.
369 * On success, returns a pointer to where the block of data should be written.
370 * On failure, returns NIL.
371 */
372inline void *ucache_insert(struct file_ent_s *fent,
373                    uint64_t offset,
374                    uint16_t *block_ndx
375)
376{
377    lock_lock(ucache_lock);
378    void * retVal = insert_mem(fent, offset, block_ndx);
379    lock_unlock(ucache_lock);
380    return (retVal);
381}
382
383#if 0
384/**
385 * Removes a cached block of data from mtbl
386 * Returns 1 on success, 0 on failure.
387 */
388int ucache_remove(struct file_ent_s *fent, uint64_t offset)
389{
390    int rc = 0;
391    lock_lock(ucache_lock);
392    rc = remove_mem(fent , offset);
393    lock_unlock(ucache_lock);
394    return rc;
395}
396#endif
397
398/**
399 * Flushes the entire ucache's dirty blocks (every file's dirty blocks)
400 * Returns 0 on success, -1 on failure
401 */
402int ucache_flush_cache(void)
403{
404    int rc = 0;
405    lock_lock(ucache_lock);
406    struct file_table_s *ftbl = &ucache->ftbl;
407    int i;
408    for(i = 0; i < FILE_TABLE_HASH_MAX; i++)
409    {
410        if((ftbl->file[i].tag_handle != NIL64) &&
411               (ftbl->file[i].tag_handle != 0))
412        {
413            /* Iterate accross file table chain. */
414            uint16_t j;
415            for(j = i; !file_done(j); j = file_next(ftbl, j))
416            {
417                rc = flush_file(&ftbl->file[j]);
418                if(rc !=0)
419                {
420                    rc = -1;
421                    goto done;
422                }
423            }
424        }
425    }
426
427done:
428    lock_unlock(ucache_lock);
429    return rc;
430}
431
432/**
433 * Externally visible wrapper of the internal flush file function.
434 * This is intended to allow and external flush file call which locks the
435 * global lock, flushes the file, then releases the global lock.
436 * To prevent deadlock, do not call this in any function that aquires the
437 * global lock.
438 * Returns 0 on success, -1 on failure.
439 */
440int ucache_flush_file(struct file_ent_s *fent)
441{
442    int rc = 0;
443    lock_lock(ucache_lock);
444    rc = flush_file(fent);
445    lock_unlock(ucache_lock);
446    return rc;
447}
448
449/**
450 * Internal only function - Flushes dirty blocks to the I/O Nodes
451 * Returns 0 on success and -1 on failure.
452 */
453int flush_file(struct file_ent_s *fent)
454{
455    int rc = 0;
456
457    struct mem_table_s *mtbl = get_mtbl(fent->mtbl_blk, fent->mtbl_ent);
458
459    uint16_t i;
460    uint16_t temp_next = NIL16;
461    for(i = mtbl->dirty_list; !dirty_done(i); i = temp_next)
462    {
463        struct mem_ent_s *ment = &(mtbl->mem[i]);
464        if(ment->tag == NIL64 || ment->item == NIL16)
465        {
466            break;
467        }
468
469        /* Aquire block lock - TODO:check if this is redundant due to global lock */
470        ucache_lock_t *blk_lock = get_lock(ment->item);
471        lock_lock(blk_lock);
472
473        temp_next = mtbl->mem[i].dirty_next;
474        mtbl->mem[i].dirty_next = NIL16;
475
476        /*#ifdef FILE_SYSTEM_ENABLED*/
477        PVFS_object_ref ref = {fent->tag_handle, fent->tag_id, 0};
478        struct iovec vector = {&(ucache->b[ment->item].mblk[0]), CACHE_BLOCK_SIZE_K * 1024};
479        rc = iocommon_vreadorwrite(2, &ref, ment->tag, 1, &vector);
480        /*
481        #endif
482        #ifndef FILE_SYSTEM_ENABLED
483        rc = 0;
484        #endif
485        */
486
487        lock_unlock(blk_lock);
488        if(rc == -1)
489        {
490           goto done;
491        }
492    }
493
494    mtbl->dirty_list = NIL16;
495    rc = 0;
496
497done:
498    return rc;
499}
500
501/**
502 * This function is meant to be called only inside remove_mem.
503 * Returns 0 on success, -1 on failure
504 */
505int flush_block(struct file_ent_s *fent, struct mem_ent_s *ment)
506{
507    int rc = 0;
508    PVFS_object_ref ref = {fent->tag_handle, fent->tag_id, 0};
509    struct iovec vector = {&(ucache->b[ment->item].mblk[0]), CACHE_BLOCK_SIZE_K * 1024};
510    rc = iocommon_vreadorwrite(2, &ref, ment->tag, 1, &vector);
511    return rc;
512}
513
514
515/**
516 * For testing purposes only!
517 */
518int wipe_ucache(void)
519{
520    int rc = 0;
521
522    /* Aquire pointers to shmem segments (just ucache) */
523    int shmflg = SVSHM_MODE;
524
525    /* ucache */
526    key_t key = ftok(KEY_FILE, SHM_ID2);
527    int ucache_shmid = shmget(key, 0, shmflg);
528    if(ucache_shmid == -1)
529    {
530        perror("wipe_ucache - ucache shmget");
531        return -1;
532    }
533    ucache = (union user_cache_u *)shmat(ucache_shmid, NULL, 0);
534    if((long int)ucache == -1)
535    {
536        perror("wipe ucache - ucache shmat");
537        return -1;
538    }
539
540    /* wipe the cache, locks, and reinitialize */
541    memset(ucache, 0, CACHE_SIZE);
542
543    /* Force Re-creation of ftbl */
544    rc = ucache_init_file_table(1);
545    return rc;
546}
547
548/**
549 * Removes all memory entries in the mtbl corresponding to the file info
550 * provided as parameters. It also removes the mtbl and the file entry from
551 * the cache.
552 */
553int ucache_close_file(struct file_ent_s *fent)
554{
555    int rc = 0;
556    rc = lock_lock(ucache_lock);
557    rc = remove_file(fent);
558    lock_unlock(ucache_lock);
559    return rc;
560}
561
562/** May dump stats to log file if the envar LOG_UCACHE_STATS is set to 1.
563 *
564 */
565#if 0
566void log_ucache_stats(void)
567{
568    /* Return if envar not set to 1 */
569    char *var = getenv("LOG_UCACHE_STATS");
570    if(!var)
571    {
572        return;
573    }
574    if(atoi(var) != 1)
575    {
576        return;
577    }
578
579    float attempts = these_stats.hits + these_stats.misses;
580    float percentage = 0.0;
581    /* Don't Divide By Zero! */
582    if(attempts)
583    {
584        percentage = ((float)these_stats.hits) / attempts;
585    }
586   /*
587    gossip_debug(GOSSIP_UCACHE_DEBUG,
588        "user cache statistics for this execution:\n"
589        "\thits=\t%llu\n"
590        "\tmisses=\t%llu\n"
591        "\thit percentage=\t%f\n"
592        "\tpseudo_misses=\t%llu\n"
593        "\tblock_count=\t%hu\n"
594        "\tfile_count=\t%hu\n",
595        (long long unsigned int) these_stats.hits,
596        (long long unsigned int) these_stats.misses,
597        percentage,
598        (long long unsigned int) these_stats.pseudo_misses,
599        these_stats.block_count,
600        these_stats.file_count
601    );
602   */
603}
604#endif
605
606/**
607 * Dumps all cache related information to the specified file pointer.
608 * Returns 0 on succes, -1 on failure meaning the ucache wasn't enabled
609 * for some reason.
610 */
611int ucache_info(FILE *out, char *flags)
612{
613    if(!ucache_enabled)
614    {
615        ucache_initialize();
616    }
617    if(!ucache_enabled)
618    {
619        //fprintf(out, "ucache is not enabled. See ucache.log and ucached.log.\n");
620        return -1;
621    }
622   
623    /* Decide what to show */
624    unsigned char show_all = 0;
625    unsigned char show_summary = 0;
626    unsigned char show_parameters = 0;
627    unsigned char show_contents = 0;
628    unsigned char show_free = 0;
629
630    int char_ndx;
631    for (char_ndx=0; char_ndx<strlen(flags); char_ndx++)
632    {
633        char c = flags[char_ndx];
634        switch(c)
635        {
636            case 'a':
637                show_all = 1;
638                break;
639            case 's':
640                show_summary = 1;             
641                break;
642            case 'p':
643                show_parameters = 1;
644                break;
645            case 'c':
646                show_contents = 1;
647                break;
648            case 'f':
649                show_free = 1;
650                break;
651        }
652    }
653
654    float attempts = ucache_stats->hits + ucache_stats->misses;
655    float percentage = 0.0;
656
657    /* Don't Divide By Zero! */
658    if(attempts)
659    {
660        percentage = ((float) ucache_stats->hits) / attempts;
661    }
662
663    if(show_all || show_summary)
664    {
665        fprintf(out,
666            "user cache statistics:\n"
667            "\thits=\t%llu\n"
668            "\tmisses=\t%llu\n"
669            "\thit percentage=\t%f\n"
670            "\tpseudo_misses=\t%llu\n"
671            "\tblock_count=\t%hu\n"
672            "\tfile_count=\t%hu\n",
673            (long long unsigned int) ucache_stats->hits,
674            (long long unsigned int) ucache_stats->misses,
675            percentage * 100,
676            (long long unsigned int) ucache_stats->pseudo_misses,
677            ucache_stats->block_count,
678            ucache_stats->file_count
679        );
680    }
681
682    if(show_all || show_parameters)
683    {
684
685        fprintf(out, "\n#defines:\n");
686        /* First, print many of the #define values */
687        fprintf(out, "MEM_TABLE_ENTRY_COUNT = %d\n", MEM_TABLE_ENTRY_COUNT);
688        fprintf(out, "FILE_TABLE_ENTRY_COUNT = %d\n", FILE_TABLE_ENTRY_COUNT);
689        fprintf(out, "CACHE_BLOCK_SIZE_K = %d\n", CACHE_BLOCK_SIZE_K);
690        fprintf(out, "MEM_TABLE_HASH_MAX = %d\n", MEM_TABLE_HASH_MAX);
691        fprintf(out, "FILE_TABLE_HASH_MAX = %d\n", FILE_TABLE_HASH_MAX);
692        fprintf(out, "MTBL_PER_BLOCK  = %d\n", MTBL_PER_BLOCK );
693        fprintf(out, "KEY_FILE = %s\n", KEY_FILE);
694        fprintf(out, "SHM_ID1 = %d\n", SHM_ID1);
695        fprintf(out, "SHM_ID2 = %d\n", SHM_ID2);
696        fprintf(out, "BLOCKS_IN_CACHE = %d\n", BLOCKS_IN_CACHE);
697        fprintf(out, "CACHE_SIZE = %d(B)\t%d(MB)\n", CACHE_SIZE,
698                                        (CACHE_SIZE/(1024*1024)));
699        fprintf(out, "AT_FLAGS = %d\n", AT_FLAGS);
700        fprintf(out, "SVSHM_MODE = %d\n", SVSHM_MODE);
701        fprintf(out, "CACHE_FLAGS = %d\n", CACHE_FLAGS);
702        fprintf(out, "NIL = 0X%X\n", NIL);
703        fprintf(out, "NIL8 = 0X%X\n", NIL8);
704        fprintf(out, "NIL16 = 0X%X\n", NIL16);   
705        fprintf(out, "NIL32 = 0X%X\n", NIL32);
706        fprintf(out, "NIL64 = 0X%lX\n", NIL64);
707   
708        /* Print sizes of ucache elements */
709        fprintf(out, "sizeof union cache_block_u = %lu\n", sizeof(union cache_block_u));
710        fprintf(out, "sizeof struct file_table_s = %lu\n", sizeof(struct file_table_s));
711        fprintf(out, "sizeof struct file_ent_s = %lu\n", sizeof(struct file_ent_s));
712        fprintf(out, "sizeof struct mem_table_s = %lu\n", sizeof(struct mem_table_s));
713        fprintf(out, "sizeof struct mem_ent_s = %lu\n", sizeof(struct mem_ent_s));
714    }
715
716    if(show_all || show_contents)
717    {
718        /* Auxilliary structure related to ucache */
719        fprintf(out, "ucache_aux ptr:\t\t0X%lX\n", (long int)ucache_aux);
720
721        /* ucache Shared Memory Info */
722        fprintf(out, "ucache ptr:\t\t0X%lX\n", (long int)ucache);
723   
724        /* FTBL Info */
725        struct file_table_s *ftbl = &(ucache->ftbl);
726        fprintf(out, "ftbl ptr:\t\t0X%lX\n", (long int)&(ucache->ftbl));
727        fprintf(out, "free_blk = %hu\n", ftbl->free_blk);
728        fprintf(out, "free_mtbl_blk = %hu\n", ftbl->free_mtbl_blk);
729        fprintf(out, "free_mtbl_ent = %hu\n", ftbl->free_mtbl_ent);
730        fprintf(out, "free_list = %hu\n", ftbl->free_list);
731   
732        uint16_t i;
733
734        if(show_all || show_free)
735        {
736            /* Other Free Blocks */
737            fprintf(out, "\nIterating Over Free Blocks:\n\n");
738            for(i = ftbl->free_blk; i < BLOCKS_IN_CACHE; i = ucache->b[i].mtbl[0].
739                                                                      free_list_blk)
740            {
741                fprintf(out, "Free Block:\tCurrent: %hu\tNext: %hu\n", i,
742                                       ucache->b[i].mtbl[0].free_list_blk);
743            }
744            fprintf(out, "End of Free Blocks List\n");
745
746
747            /* Iterate Over Free Mtbls */
748            fprintf(out, "\nIterating Over Free Mtbls:\n");
749            uint16_t current_blk = (uint16_t)ftbl->free_mtbl_blk;
750            uint16_t current_ent = ftbl->free_mtbl_ent;
751            while(current_blk != NIL16)
752            {
753                fprintf(out, "free mtbl: block = %hu\tentry = %hu\n",
754                        current_blk, current_ent);
755                uint16_t temp_blk = ucache->b[current_blk].mtbl[current_ent].free_list_blk;
756                uint16_t temp_ent = ucache->b[current_blk].mtbl[current_ent].free_list;
757                current_blk = temp_blk;
758                current_ent = temp_ent;
759            }
760            fprintf(out, "End of Free Mtbl List\n\n");
761       
762            /* Iterating Over Free File Entries */
763            fprintf(out, "Iterating Over Free File Entries:\n");
764            uint16_t current_fent;
765            for(current_fent = ftbl->free_list; current_fent != NIL16;
766                                current_fent = ftbl->file[current_fent].next)
767            {
768                fprintf(out, "free file entry: index = %d\n", (int16_t)current_fent);
769            }
770            fprintf(out, "End of Free File Entry List\n\n");
771        }
772   
773        fprintf(out, "Iterating Over File Entries in Hash Table:\n\n");
774        /* iterate over file table entries */
775        for(i = 0; i < FILE_TABLE_HASH_MAX; i++)
776        {
777            if((ftbl->file[i].tag_handle != NIL64) &&
778                   (ftbl->file[i].tag_handle != 0))
779            {
780                /* iterate accross file table chain */
781                uint16_t j;
782                for(j = i; !file_done(j); j = file_next(ftbl, j))
783                {
784                    fprintf(out, "FILE ENTRY INDEX %hu ********************\n", j);
785                    struct file_ent_s * fent = &(ftbl->file[j]);
786                    fprintf(out, "tag_handle = 0X%llX\n",
787                                (long long int)fent->tag_handle);
788                    fprintf(out, "tag_id = 0X%X\n", (uint32_t)fent->tag_id);
789                    fprintf(out, "mtbl_blk = %hu\n", fent->mtbl_blk);
790                    fprintf(out, "mtbl_ent = %hu\n", fent->mtbl_ent);
791                    fprintf(out, "next = %hu\n", fent->next);
792                    fprintf(out, "index = %hu\n", fent->index);
793   
794                    struct mem_table_s * mtbl = get_mtbl(fent->mtbl_blk,
795                                                        fent->mtbl_ent);
796   
797                    fprintf(out, "\tMTBL LRU List ****************\n");
798                    print_LRU(mtbl);
799                    print_dirty(mtbl);
800   
801                    fprintf(out, "\tMTBL INFO ********************\n");
802                    fprintf(out, "\tnum_blocks = %hu\n", mtbl->num_blocks);
803                    fprintf(out, "\tfree_list = %hu\n", mtbl->free_list);
804                    fprintf(out, "\tfree_list_blk = %hu\n", mtbl->free_list_blk);
805                    fprintf(out, "\tlru_first = %hu\n", mtbl->lru_first);
806                    fprintf(out, "\tlru_last = %hu\n", mtbl->lru_last);
807                    fprintf(out, "\tdirty_list = %hu\n", mtbl->dirty_list);
808                    fprintf(out, "\tref_cnt = %hu\n\n", mtbl->ref_cnt);
809                    fflush(out);
810                    /* Iterate Over Memory Entries */
811                    uint16_t k;
812                    for(k = 0; k < MEM_TABLE_HASH_MAX; k++)
813                    {
814                        if(mtbl->bucket[k] == NIL16)
815                            continue;
816   
817                        if(mtbl->mem[mtbl->bucket[k]].tag != NIL64)
818                        {
819                            uint16_t l;
820                            for(l = mtbl->bucket[k]; !ment_done(l); l = ment_next(mtbl, l))
821                            {
822                                struct mem_ent_s * ment = &(mtbl->mem[l]);
823                                fprintf(out, "\t\tMEMORY ENTRY INDEX %hd **********"
824                                                                  "*********\n", l);
825                                fprintf(out, "\t\ttag = 0X%lX\n",
826                                             (long unsigned int)ment->tag);
827
828                                fprintf(out, "\t\titem = %hu\n",
829                                                    ment->item);
830                                fprintf(out, "\t\tnext = %hu\n",
831                                                    ment->next);
832                                fprintf(out, "\t\tdirty_next = %hu\n",
833                                                    ment->dirty_next);
834                                fprintf(out, "\t\tlru_next = %hu\n",
835                                                    ment->lru_next);
836                                fprintf(out, "\t\tlru_prev = %hu\n\n",
837                                                      ment->lru_prev);
838                            }
839                        }
840                        else
841                        {
842                            if(mtbl->num_blocks != 0
843                                && (show_all || show_free))
844                            {
845                                fprintf(out, "\tvacant memory entry @ index = %d\n",
846                                    mtbl->bucket[k]);
847                            }
848                        }
849                    }
850                }
851                fprintf(out, "End of chain @ Hash Table Index %hu\n\n", i);
852            }
853            else
854            {
855                if(show_all || show_free)
856                {
857                    fprintf(out, "vacant file entry @ index = %hu\n\n", i);
858                }
859            }
860        }
861    }
862    return 0;
863}
864
865/**
866 * Returns a pointer to the lock corresponding to the block_index.
867 * If the index is out of range, then 0 is returned.
868 */
869inline ucache_lock_t *get_lock(uint16_t block_index)
870{
871    if(block_index >= (BLOCKS_IN_CACHE + 1))
872    {
873        return (ucache_lock_t *)0;
874    }
875    return &ucache_locks[block_index];
876}
877
878/**
879 * Initializes the proper lock based on the LOCK_TYPE
880 * Returns 0 on success, -1 on error
881 */
882int lock_init(ucache_lock_t * lock)
883{
884    int rc = -1;
885    /* TODO: ability to disable locking */
886    #if LOCK_TYPE == 0
887    rc = sem_init(lock, 1, 1);
888    if(rc != -1)
889    {
890        rc = 0;
891    }
892    #elif LOCK_TYPE == 1
893    pthread_mutexattr_t attr;
894    rc = pthread_mutexattr_init(&attr);
895    assert(rc == 0);
896    rc = pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
897    assert(rc == 0);
898    rc = pthread_mutex_init(lock, &attr);
899    assert(rc == 0);
900    if(rc != 0)
901    {
902        return -1;
903    }
904    #elif LOCK_TYPE == 2
905    rc = pthread_spin_init(lock, 1);
906    if(rc != 0)
907    {
908        return -1;
909    }
910    #elif LOCK_TYPE == 3
911    *lock = (ucache_lock_t) GEN_SHARED_MUTEX_INITIALIZER_NP; //GEN_SHARED_MUTEX_INITIALIZER_NP;
912    rc = 0;
913    #endif
914    return rc;
915}
916
917/**
918 * Returns 0 when lock is locked; otherwise, return -1 and sets errno.
919 */
920inline int lock_lock(ucache_lock_t * lock)
921{
922    int rc = 0;
923    #if LOCK_TYPE == 0
924    return sem_wait(lock);
925    #elif LOCK_TYPE == 1
926/*
927    while(1)
928    {
929        rc = pthread_mutex_trylock(lock);
930        if(rc != 0)
931        {
932            printf("couldn't lock lock 0X%lX\n", (long unsigned int) lock);
933            fflush(stdout);
934            rc = -1;
935        }
936        else
937        {
938            break;
939        }
940    }
941*/
942    rc = pthread_mutex_lock(lock);
943    return rc;
944    #elif LOCK_TYPE == 2
945    return pthread_spin_lock(lock);
946    #elif LOCK_TYPE == 3
947    rc = gen_mutex_lock(lock);
948    return rc;
949    #endif   
950}
951
952/**
953 * If successful, return zero; otherwise, return -1 and sets errno.
954 */
955inline int lock_unlock(ucache_lock_t * lock)
956{
957    #if LOCK_TYPE == 0
958    return sem_post(lock);
959    #elif LOCK_TYPE == 1
960    return pthread_mutex_unlock(lock);
961    #elif LOCK_TYPE == 2
962    return pthread_spin_unlock(lock);
963    #elif LOCK_TYPE == 3
964    return gen_mutex_unlock(lock);
965    #endif
966}
967
968/**
969 * Upon successful completion, returns zero
970 * Otherwise, returns -1 and sets errno.
971 */
972#if (LOCK_TYPE == 0)
973int ucache_lock_getvalue(ucache_lock_t * lock, int *sval)
974{
975    return sem_getvalue(lock, sval);
976}
977#endif
978
979/**
980 * Tries the lock to see if it's available:
981 * Returns 0 if lock has not been aquired ie: success
982 * Otherwise, returns -1
983 */
984inline int lock_trylock(ucache_lock_t * lock)
985{
986    int rc = -1;
987    #if (LOCK_TYPE == 0)
988    int sval = 0;
989    rc = sem_getvalue(lock, &sval);
990    if(sval <= 0 || rc == -1){
991        rc = -1;
992    }
993    else
994    {
995        rc = 0;
996    }
997    #elif (LOCK_TYPE == 1)
998    rc = pthread_mutex_trylock(lock);
999    if( rc != 0)
1000    {
1001        rc = -1;
1002    }
1003    #elif (LOCK_TYPE == 2)
1004    rc = pthread_spin_trylock(lock);
1005    if(rc != 0)
1006    {
1007        rc = -1;
1008    }
1009    #elif LOCK_TYPE == 3
1010    rc = gen_mutex_trylock(lock);
1011    if(rc != 0)
1012    {
1013        rc = -1;
1014    }
1015    #endif
1016    if(rc == 0)
1017    {
1018        /* Unlock before leaving if lock wasn't already set */
1019        rc = lock_unlock(lock);
1020    }
1021    return rc;
1022}
1023/***************************************** End of Externally Visible API */
1024
1025/* Beginning of internal only (static) functions */
1026
1027/* Dirty List Iterator */
1028/**
1029 * Returns true if current index is NIL, otherwise, returns 0.
1030 */
1031static inline unsigned char dirty_done(uint16_t index)
1032{
1033    return (index == NIL16);
1034}
1035
1036/**
1037 * Returns the next index in the dirty list for the provided mtbl and index
1038 */
1039static inline uint16_t dirty_next(struct mem_table_s *mtbl, uint16_t index)
1040{
1041    return mtbl->mem[index].dirty_next;
1042}
1043
1044/*  Memory Entry Chain Iterator */
1045/**
1046 * Returns true if current index is NIL, otherwise, returns 0.
1047 */
1048static inline unsigned char ment_done(uint16_t index)
1049{
1050    return (index == NIL16);
1051}
1052
1053/**
1054 * Returns the next index in the memory entry chain for the provided mtbl
1055 * and index.
1056 */
1057static inline uint16_t ment_next(struct mem_table_s *mtbl, uint16_t index)
1058{
1059    return mtbl->mem[index].next;
1060}
1061
1062/*  File Entry Chain Iterator   */
1063/**
1064 * Returns true if current index is NIL, otherwise, returns 0
1065 */
1066static unsigned char file_done(uint16_t index)
1067{
1068    return (index == NIL16);
1069}
1070
1071/**
1072 * Returns the next index in the file entry chain for the provided mtbl
1073 * and index.
1074 */
1075static uint16_t file_next(struct file_table_s *ftbl, uint16_t index)
1076{
1077    return ftbl->file[index].next;
1078}
1079
1080/**
1081 * This function should only be called when the ftbl has no free mtbls.
1082 * It initizializes MTBL_PER_BLOCK additional mtbls in the block provided,
1083 * meaning this block will no longer be used for storing file data but
1084 * hash table related data instead.
1085 */
1086static void add_mtbls(uint16_t blk)
1087{
1088    uint16_t i, start_mtbl;
1089    struct file_table_s *ftbl = &(ucache->ftbl);
1090    union cache_block_u *b = &(ucache->b[blk]);
1091
1092    /* add mtbls in blk to ftbl free list */
1093    if (blk == 0)
1094    {
1095        start_mtbl = 1; /* skip blk 0 ent 0 which is ftbl */
1096    }
1097    else
1098    {
1099        start_mtbl = 0;
1100    }
1101    for (i = start_mtbl; i < (MTBL_PER_BLOCK - 1); i++)
1102    {
1103        b->mtbl[i].free_list_blk = blk;
1104        b->mtbl[i].free_list = i + 1;
1105    }
1106    b->mtbl[i].free_list_blk = NIL16;
1107    b->mtbl[i].free_list = NIL16;
1108    ftbl->free_mtbl_blk = blk;
1109    ftbl->free_mtbl_ent = start_mtbl;   
1110}
1111/**
1112 * Initializes a memory entry.
1113 */
1114static inline int init_memory_entry(struct mem_table_s *mtbl, int16_t index)
1115{
1116        if(index > MEM_TABLE_ENTRY_COUNT)
1117        {
1118            return -1;
1119        }
1120        mtbl->mem[index].tag = NIL64;
1121        mtbl->mem[index].item = NIL16;
1122        mtbl->mem[index].next = NIL16;
1123        mtbl->mem[index].dirty_next = NIL16;
1124        mtbl->mem[index].lru_prev = NIL16;
1125        mtbl->mem[index].lru_next = NIL16;
1126        return 0;
1127}
1128
1129/**
1130 * Initializes a mtbl which is a hash table of memory entries.
1131 * The mtbl will be located at the provided entry index within
1132 * the provided block.
1133 */
1134static void init_memory_table(struct mem_table_s *mtbl)
1135{
1136    uint16_t i;
1137    int rc = -1;
1138    mtbl->num_blocks = 0;
1139    mtbl->free_list_blk = NIL16;
1140    mtbl->lru_first = NIL16;
1141    mtbl->lru_last = NIL16;
1142    mtbl->dirty_list = NIL16;
1143    mtbl->ref_cnt = 0;
1144
1145    /* Initialize Buckets */
1146    for(i = 0; i < MEM_TABLE_HASH_MAX; i++)
1147    {
1148        mtbl->bucket[i] = NIL16;
1149    }
1150
1151    /* set up free ments */
1152    mtbl->free_list = 0;
1153    for(i = 0; i < (MEM_TABLE_ENTRY_COUNT - 1); i++)
1154    {
1155        rc = init_memory_entry(mtbl, i);
1156        mtbl->mem[i].next = i + 1;
1157
1158    }
1159    /* NIL Terminate the last entries next index */
1160    rc = init_memory_entry(mtbl, MEM_TABLE_ENTRY_COUNT - 1);
1161    mtbl->mem[MEM_TABLE_ENTRY_COUNT - 1].next = NIL16;
1162}
1163
1164/**
1165 * This function asks the file table if a free block is avaialable.
1166 * If so, returns the block's index; otherwise, returns NIL.
1167 */
1168static inline uint16_t get_free_blk(void)
1169{
1170    struct file_table_s *ftbl = &(ucache->ftbl);
1171    uint16_t desired_blk = ftbl->free_blk;
1172    if(desired_blk != NIL16 && desired_blk < BLOCKS_IN_CACHE)
1173    { 
1174        /* Update the head of the free block list */
1175        /* Use mtbl index zero since free_blks have no ititialized mem tables */
1176        ftbl->free_blk = ucache->b[desired_blk].mtbl[0].free_list_blk;
1177        return desired_blk;
1178    }
1179    return NIL16;
1180}
1181
1182/**
1183 * Accepts an index corresponding to a block that is put back on the file
1184 * table free list.
1185 */
1186static inline void put_free_blk(uint16_t blk)
1187{
1188    struct file_table_s *ftbl = &(ucache->ftbl);
1189    /* set the block's next value to the current head of the block free list */
1190    ucache->b[blk].mtbl[0].free_list_blk = ftbl->free_blk;
1191    /* blk is now the head of the ftbl blk free list */
1192    ftbl->free_blk = blk;
1193}
1194
1195/**
1196 * Consults the file table to retrieve an index corresponding to a file entry
1197 * If available, returns the file entry index, otherwise returns NIL.
1198 */
1199static uint16_t get_free_fent(void)
1200{
1201    struct file_table_s *ftbl = &(ucache->ftbl);
1202    uint16_t entry = ftbl->free_list;
1203    if(entry != NIL16)
1204    {
1205        ftbl->free_list = ftbl->file[entry].next;
1206        ftbl->file[entry].next = NIL16;
1207        return entry;
1208    }
1209    else
1210    {
1211        return NIL16;
1212    }
1213}
1214
1215/**
1216 * Places the file entry located at the provided index back on the file table's
1217 * free file entry list. If the index is < FILE_TABLE_HASH_MAX, then set next
1218 * to NIL since this index must remain the head of the linked list. Otherwise,
1219 * set next to the current head of fent free list and set the free list head to
1220 * the provided index.
1221 */
1222static void put_free_fent(struct file_ent_s *fent)
1223{
1224    struct file_table_s *ftbl = &(ucache->ftbl);
1225    fent->tag_handle = NIL64;
1226    fent->tag_id = NIL32;
1227    if(fent->index < FILE_TABLE_HASH_MAX)
1228    {
1229        fent->next = NIL16;
1230    }
1231    else
1232    {
1233        /* Set next index to the current head of the free list */
1234        fent->next = ftbl->free_list;
1235        /* Set fent index as the head of the free_list */
1236        ftbl->free_list = fent->index;
1237    }
1238}
1239
1240/**
1241 * Consults the provided mtbl's memory entry free list to get the index of the
1242 * next free memory entry. Returns the index if one is available, otherwise
1243 * returns NIL.
1244 */
1245static inline uint16_t get_free_ment(struct mem_table_s *mtbl)
1246{
1247    uint16_t ment = mtbl->free_list;
1248    if(ment != NIL16)
1249    {
1250        mtbl->free_list = mtbl->mem[ment].next;
1251        mtbl->mem[ment].next = NIL16;
1252    }
1253    return ment;
1254}
1255
1256/**
1257 * Puts the memory entry corresponding to the provided mtbl and entry index
1258 * back on the mtbl's memory entry free list.
1259 */
1260static void put_free_ment(struct mem_table_s *mtbl, uint16_t ent)
1261{
1262    /* Reset ment values */
1263    mtbl->mem[ent].tag = NIL64;
1264    mtbl->mem[ent].item = NIL16;
1265    mtbl->mem[ent].dirty_next = NIL16;
1266    mtbl->mem[ent].lru_prev = NIL16;
1267    mtbl->mem[ent].lru_next = NIL16;
1268    /* Set next index to the current head of the free list */
1269    mtbl->mem[ent].next = mtbl->free_list;
1270    /* Update free list to include this entry */
1271    mtbl->free_list = ent;
1272}
1273
1274/**
1275 * Perform a file lookup on the ucache using the provided fs_id and handle.
1276 *
1277 * Additional parameters (references) may used that will be set to values
1278 * pertaining to mtbl and file entry location. If NULL is passed in place of
1279 * these parameters, then they cannot be set.
1280 *
1281 * If the file is found, a pointer to the mtbl is returned and the parameter
1282 * references set accordingly. Otherwise, NIL is returned.
1283 */
1284static struct mem_table_s *lookup_file(
1285    uint32_t fs_id,
1286    uint64_t handle,
1287    uint16_t *file_mtbl_blk,
1288    uint16_t *file_mtbl_ent,
1289    uint16_t *file_ent_index,
1290    uint16_t *file_ent_prev_index
1291)
1292{
1293    /* Index into file hash table */
1294    uint16_t index = handle % FILE_TABLE_HASH_MAX;
1295
1296    struct file_table_s *ftbl = &(ucache->ftbl);
1297    struct file_ent_s *current = &(ftbl->file[index]);
1298
1299    /* previous, current, next fent index */
1300    uint16_t p = NIL16;
1301    uint16_t c = index;
1302    uint16_t n = current->next;
1303
1304    while(1)
1305    {
1306        if((current->tag_id == fs_id) && (current->tag_handle == handle))
1307        {
1308            /* If params !NULL, set their values */
1309            if(file_mtbl_blk!=NULL && file_mtbl_ent!=NULL &&
1310                file_ent_index!=NULL && file_ent_prev_index!=NULL)
1311            {
1312                    *file_mtbl_blk = current->mtbl_blk;
1313                    *file_mtbl_ent = current->mtbl_ent;
1314                    *file_ent_index = c;
1315                    *file_ent_prev_index = p;
1316            }
1317            return (struct mem_table_s *)&(ucache->b[current->mtbl_blk].mtbl[
1318                                                            current->mtbl_ent]);
1319        }
1320        /* No match yet */
1321        else   
1322        {
1323            if(current->next == NIL16 || current->next == 0)
1324            {
1325                return (struct mem_table_s *)NIL;
1326            }
1327            else
1328            {
1329                current = &(ftbl->file[current->next]);
1330                p=c;
1331                c=n;
1332                n=current->next;
1333            }
1334
1335        }
1336    }   
1337}
1338
1339/**
1340 * Function that locates the next free mtbl.
1341 * On success, Returns 1 and sets reference parameters to proper indexes.
1342 * On failure, returns NIL;
1343 */
1344static uint16_t get_next_free_mtbl(uint16_t *free_mtbl_blk, uint16_t *free_mtbl_ent)
1345{
1346        struct file_table_s *ftbl = &(ucache->ftbl);
1347
1348        /* Get next free mtbl_blk and ent from ftbl */
1349        *free_mtbl_blk = ftbl->free_mtbl_blk;
1350        *free_mtbl_ent = ftbl->free_mtbl_ent;
1351
1352        /* Is free mtbl_blk available? */
1353        if((*free_mtbl_blk == NIL16) ||
1354             (*free_mtbl_ent == NIL16))
1355        {
1356            return NIL16;
1357        }
1358
1359        /* Update ftbl to contain new next free mtbl */
1360        ftbl->free_mtbl_blk = ucache->b[*free_mtbl_blk].mtbl[*free_mtbl_ent].
1361                                                                free_list_blk;
1362        ftbl->free_mtbl_ent = ucache->b[*free_mtbl_blk].mtbl[*free_mtbl_ent].
1363                                                                    free_list;
1364
1365        /* Set free info to NIL */
1366        ucache->b[*free_mtbl_blk].mtbl[*free_mtbl_ent].free_list = NIL16;
1367        ucache->b[*free_mtbl_blk].mtbl[*free_mtbl_ent].free_list_blk = NIL16;
1368
1369        return 1;
1370}
1371
1372/**
1373 * Places memory entries' corresponding blocks
1374 * back on the ftbl block free list. Reinitializes mtbl.
1375 * Assumes mtbl->ref_cnt is 0. 
1376 */
1377static int wipe_mtbl(struct mem_table_s *mtbl)
1378{
1379    uint16_t i;
1380    for(i = 0; i < MEM_TABLE_HASH_MAX; i++)
1381    {
1382        uint16_t j;
1383        for(j = mtbl->bucket[i]; !ment_done(j); j = ment_next(mtbl, j))
1384        {
1385            /* Current Memory Entry */
1386            struct mem_ent_s *ment = &(mtbl->mem[j]);
1387            /*  Account for empty head of ment chain    */
1388            if((ment->tag == NIL64) || (ment->item == NIL16))
1389            {
1390                break;
1391            }
1392            put_free_blk(ment->item);
1393        }
1394    }
1395    memset(&mtbl->mem[0], 0, sizeof(struct mem_ent_s) * MEM_TABLE_ENTRY_COUNT);
1396    init_memory_table(mtbl);
1397    return 1;
1398}
1399
1400/**
1401 * Places the provided mtbl back on the ftbl's mtbl free list provided it
1402 * isn't currently referenced.
1403 */
1404static int put_free_mtbl(struct mem_table_s *mtbl, struct file_ent_s *file)
1405{
1406    /* Remove mtbl */
1407    mtbl->num_blocks = 0;   /* number of used blocks in this mtbl */
1408    mtbl->lru_first = NIL16;  /* index of first block on lru list */
1409    mtbl->lru_last = NIL16;   /* index of last block on lru list */
1410    mtbl->dirty_list = NIL16; /* index of first dirty block */
1411    mtbl->ref_cnt = 0;      /* number of clients using this record */
1412
1413    /* Add mem_table back to free list */
1414    /* Temporarily store copy of current head (the new next) */
1415    uint16_t tmp_blk = ucache->ftbl.free_mtbl_blk;
1416    uint16_t tmp_ent = ucache->ftbl.free_mtbl_ent;
1417    /* newly free mtbl becomes new head of free mtbl list */
1418    ucache->ftbl.free_mtbl_blk = file->mtbl_blk;
1419    ucache->ftbl.free_mtbl_ent = file->mtbl_ent;
1420    /* Point to the next free mtbl (the former head) */
1421    mtbl->free_list_blk = tmp_blk;
1422    mtbl->free_list = tmp_ent;
1423
1424    return 1;
1425}
1426
1427/**
1428 * Insert information about file into ucache (no file data inserted)
1429 * Returns pointer to mtbl on success.
1430 *
1431 * Returns NIL if necessary data structures could not be aquired from the free
1432 * lists or through an eviction policy (meaning references are held).
1433 */
1434uint16_t insert_file(
1435    uint32_t fs_id,
1436    uint64_t handle
1437)
1438{
1439    struct file_table_s *ftbl = &(ucache->ftbl);
1440    struct file_ent_s *current;     /* Current ptr for iteration */
1441    uint16_t free_fent = NIL16;       /* Index of next free fent */
1442
1443    /* index into file hash table */
1444    uint16_t index = handle % FILE_TABLE_HASH_MAX;
1445    current = &(ftbl->file[index]);
1446
1447    unsigned char indexOccupied = (current->tag_handle != NIL64 && current->tag_id != NIL32);
1448
1449    /* Get free mtbl */
1450    uint16_t free_mtbl_blk = NIL16;
1451    uint16_t free_mtbl_ent = NIL16;
1452    /* Create free mtbls if none are available */
1453    if(get_next_free_mtbl(&free_mtbl_blk, &free_mtbl_ent) != 1)
1454    {   
1455        if(ucache->ftbl.free_blk == NIL16)
1456        {
1457            /* Evict a block from mtbl with most mem entries */
1458            struct file_ent_s *max_fent = 0;
1459            struct mem_table_s *max_mtbl;
1460            locate_max_fent(&max_fent);
1461            max_mtbl = get_mtbl(max_fent->mtbl_blk, max_fent->mtbl_ent);
1462            evict_LRU(max_fent);
1463        }
1464        /* TODO: other policy? */
1465        if(ucache->ftbl.free_blk == NIL16)
1466        {
1467
1468        }
1469        /* Intitialize memory tables */
1470        if(ucache->ftbl.free_blk != NIL16)
1471        {
1472            int16_t free_blk = get_free_blk();
1473            add_mtbls(free_blk);
1474            get_next_free_mtbl(&free_mtbl_blk, &free_mtbl_ent);
1475        }
1476        else
1477        {
1478            /* Couldn't get free mtbl - unlikely */
1479            return NIL16;
1480        }
1481    }
1482
1483    /* Now, we know which hashed chain we are trying to insert into and have a
1484     * mtbl ready to be filled.
1485     */
1486
1487    /* Insert at the head or just after the head, since we can't change the
1488     * indexing (only can change "nexts").
1489     */
1490    if(indexOccupied)
1491    {
1492        /* Certain a file entry is required */
1493        /* get free file entry and update ftbl */
1494        free_fent = get_free_fent();
1495        if(free_fent != NIL16)
1496        {
1497            uint16_t temp_next = current->next;
1498            current->next = free_fent;
1499            current = &(ftbl->file[free_fent]);
1500            current->next = temp_next; /* repair link */
1501            current->index = free_fent;
1502        }
1503        else
1504        {
1505            /* Return an error indicating the ucache is full and file couldn't
1506             * be cached
1507             */
1508            return NIL16;
1509        }
1510    }
1511    else
1512    {
1513        current->index = index;
1514    }
1515
1516    /* Insert file data @ index */
1517    current->tag_id = fs_id;
1518    current->tag_handle = handle;
1519    /* Update fent with it's new mtbl: blk and ent */
1520    current->mtbl_blk = free_mtbl_blk;
1521    current->mtbl_ent = free_mtbl_ent;
1522    /* Initialize Memory Table */
1523    init_memory_table(get_mtbl(free_mtbl_blk, free_mtbl_ent));
1524    return current->index;
1525}
1526
1527/**
1528 * Remove file entry and memory table of file identified by parameters
1529 * Returns 1 following removal
1530 * Returns -1 if file is referenced or if the file could not be located.
1531 */
1532static int remove_file(struct file_ent_s *fent)
1533{
1534    int rc = 0;
1535    struct mem_table_s *mtbl = get_mtbl(fent->mtbl_blk,
1536                                       fent->mtbl_ent);
1537
1538    if(mtbl == (struct mem_table_s *)NILP)
1539    {
1540        return -1;
1541    }
1542
1543    /* Flush file blocks before file removal */
1544    mtbl->ref_cnt--;
1545
1546    if(mtbl->ref_cnt > 0)
1547    {
1548        return 0;
1549    }
1550
1551    /* Flush dirty blocks before file removal from cache */
1552    rc = flush_file(fent);
1553    if(rc == -1)
1554    {
1555        return rc;
1556    }
1557
1558    /* Instead of removing individually, since memory entries are already
1559     * flushed, just wipe the mtbl
1560     */
1561    rc = wipe_mtbl(mtbl);
1562    if(rc == -1)
1563    {
1564        /* Couldn't remove entries */
1565        return rc;
1566    }
1567
1568    rc = put_free_mtbl(mtbl, fent);
1569    if(rc == -1)
1570    {
1571        return rc;
1572    }
1573
1574    put_free_fent(fent);
1575    if(rc == -1)
1576    {
1577        return rc;
1578    }
1579
1580    /* Success */
1581    return 0;
1582}
1583
1584/**
1585 * Lookup the memory location of a block of data in cache that is identified
1586 * by the mtbl and offset parameters.
1587 *
1588 * If located, returns a pointer to memory where the desired block of data is
1589 * stored. Otherwise, NIL is returned.
1590 *
1591 * pertaining to the memory entry's location. If NULLs are passed in place of
1592 * these parameters, then they will not be set.
1593 */
1594inline static void *lookup_mem(struct mem_table_s *mtbl,
1595                    uint64_t offset,
1596                    uint16_t *item_index,
1597                    uint16_t *mem_ent_index,
1598                    uint16_t *mem_ent_prev_index)
1599{
1600    /* index into mem hash table */
1601    uint16_t index = (uint16_t) ((offset / CACHE_BLOCK_SIZE) % MEM_TABLE_HASH_MAX);
1602
1603    /* If the bucket is empty then go ahead and return */
1604    if(mtbl->bucket[index] == NIL16)
1605    {
1606        return (struct mem_table_s *)NIL;
1607    }
1608
1609    uint16_t bucket_index = mtbl->bucket[index];
1610    struct mem_ent_s *current = &(mtbl->mem[bucket_index]);
1611
1612    /* previous, current, next memory entry index in mtbl */
1613    int16_t p = NIL16;
1614    int16_t c = bucket_index;
1615    int16_t n = current->next; 
1616
1617    while(1)
1618    {
1619        if(offset == current->tag)
1620        {
1621            /* If parameters !NULL, set their values */
1622            if(item_index != NULL)
1623            {
1624                *item_index = current->item;
1625            }
1626            if((mem_ent_index != NULL) && (mem_ent_prev_index != NULL))
1627            {
1628                    *mem_ent_index = c;
1629                    *mem_ent_prev_index = p;
1630            }
1631            return (void *)(&ucache->b[current->item].mblk);
1632        }
1633        else
1634        {
1635            if(current->next == NIL16)
1636            {
1637                return (struct mem_table_s *)NIL;
1638            }
1639            else
1640            {
1641                /* Iterate */
1642                current = &(mtbl->mem[current->next]);
1643                p = c;
1644                c = n;
1645                n = current->next;
1646            }
1647        }
1648    }
1649}
1650
1651/**
1652 * Update the provided mtbl's LRU doubly-linked list by placing the memory
1653 * entry, identified by the provided index, at the head of the list (lru_first).
1654 */
1655static inline void update_LRU(struct mem_table_s *mtbl, uint16_t index)
1656{
1657    /* First memory entry used becomes the head and tail of the list */
1658    if((mtbl->lru_first == NIL16) &&
1659        (mtbl->lru_last == NIL16))
1660    {
1661        mtbl->lru_first = index;
1662        mtbl->lru_last = index;
1663        mtbl->mem[index].lru_prev = NIL16;
1664        mtbl->mem[index].lru_next = NIL16;
1665    }
1666    /* 2nd Memory Entry */
1667    else if(mtbl->lru_first == mtbl->lru_last)
1668    {
1669        /* Do nothing if this index is already the only entry */
1670        if(mtbl->lru_first == index)
1671        {
1672            return;
1673        }
1674        else
1675        {   
1676            /* Must be 2nd unique memory entry */
1677            /* point tail.prev to new */
1678            mtbl->mem[mtbl->lru_first].lru_prev = index;
1679            /* point new.prev to NIL */ 
1680            mtbl->mem[index].lru_prev = NIL16;
1681            /* point the new.next to the tail */     
1682            mtbl->mem[index].lru_next = mtbl->lru_first;
1683            /* point the head to the new */ 
1684            mtbl->lru_first = index;                   
1685        }
1686    }
1687    /* 3rd+ Memory Entry */
1688    else
1689    {
1690        if(mtbl->mem[index].lru_prev == NIL16 &&
1691            mtbl->mem[index].lru_next == NIL16)
1692        {
1693            /* First time on the LRU List, Add to the front */
1694            mtbl->mem[index].lru_next = mtbl->lru_first;
1695            mtbl->mem[mtbl->lru_first].lru_prev = index;   
1696        }
1697        else if(mtbl->mem[index].lru_prev == NIL16)
1698        {
1699            /* Already the head of MRU */
1700            return;
1701        }
1702        else if(mtbl->mem[index].lru_next == NIL16)
1703        {
1704            /* Relocate the LRU to become the MRU */
1705            mtbl->lru_last = mtbl->mem[index].lru_prev;
1706            mtbl->mem[mtbl->lru_last].lru_next = NIL16;
1707            mtbl->mem[mtbl->lru_first].lru_prev = index;
1708            mtbl->mem[index].lru_next = mtbl->lru_first;
1709            mtbl->mem[index].lru_prev = NIL16;
1710        }
1711        else
1712        {
1713            /* Relocate interior LRU list item to head */
1714            uint16_t current_prev = mtbl->mem[index].lru_prev;
1715            uint16_t current_next = mtbl->mem[index].lru_next;
1716
1717            mtbl->mem[current_prev].lru_next = current_next;
1718            mtbl->mem[current_next].lru_prev = current_prev;
1719
1720            mtbl->mem[index].lru_prev = NIL16;
1721            mtbl->mem[index].lru_next = mtbl->lru_first;
1722        }
1723        mtbl->lru_first = index;
1724    }
1725}   
1726
1727/**
1728 * Searches the ftbl for the mtbl with the most entries.
1729 * Returns the number of memory entries the max mtbl has. The double ptr
1730 * parameter is used to store a reference to the mtbl pointer with the most
1731 * memory entries.
1732 */
1733static uint16_t locate_max_fent(struct file_ent_s **fent)
1734{
1735    struct file_table_s *ftbl = &(ucache->ftbl);
1736    uint16_t index_of_max_blk = NIL16;
1737    uint16_t index_of_max_ent = NIL16;
1738    uint16_t value_of_max = 0;
1739    /* Iterate over file hash table indices */
1740    uint16_t i;
1741    for(i = 0; i < FILE_TABLE_HASH_MAX; i++)
1742    {
1743
1744        if((ftbl->file[i].tag_handle == NIL64) ||
1745               (ftbl->file[i].tag_handle == 0))
1746            continue;
1747
1748        /* Iterate over hash table chain */
1749        uint16_t j;
1750        for(j = i; !file_done(j); j = file_next(ftbl, j))
1751        {
1752            struct file_ent_s *current_fent = &(ftbl->file[j]);
1753            if((current_fent->mtbl_blk == NIL16) ||
1754                    (current_fent->mtbl_ent == NIL16))
1755            {
1756                break;
1757            }
1758            /* Examine the mtbl's value of num_blocks to see if it's the
1759             * greatest.
1760             */
1761            struct mem_table_s *current_mtbl = get_mtbl(current_fent->mtbl_blk,
1762                                                       current_fent->mtbl_ent);
1763
1764            if(current_mtbl->num_blocks >= value_of_max)
1765            {
1766                *fent = current_fent; /* Set the parameter to this mtbl */
1767                index_of_max_blk = current_fent->mtbl_blk;
1768                index_of_max_ent = current_fent->mtbl_ent;
1769                value_of_max = current_mtbl->num_blocks;
1770            }
1771        }
1772    }
1773    return value_of_max;
1774}
1775
1776/**
1777 * Evicts the LRU memory entry from the tail (lru_last) of the provided
1778 * mtbl's LRU list.
1779 *
1780 * Returns 1 on success; 0 on failure, meaning there was no LRU
1781 * or that the block's lock couldn't be aquired.
1782 */
1783static int evict_LRU(struct file_ent_s *fent)
1784{
1785    int rc = -1;
1786   
1787    struct mem_table_s *mtbl = get_mtbl(fent->mtbl_blk, fent->mtbl_ent);
1788
1789    if(mtbl->num_blocks != 0 && mtbl->lru_last != NIL16)
1790    {
1791        //printf("evicting: %hu\n", mtbl->lru_last);
1792        rc = remove_mem(fent, mtbl->mem[mtbl->lru_last].tag);
1793        if(rc != 1)
1794        {
1795            return 0;
1796        }
1797        return 1;
1798    }
1799    else
1800    {
1801        return 0;
1802    }
1803}
1804
1805
1806/**
1807 * Used to obtain a block for storage of data identified by the offset
1808 * parameter and maintained in the mtbl at the memory entry identified by the
1809 * index parameter.
1810 *
1811 * If a free block could be aquired, returns the memory address of the block
1812 * just inserted. Otherwise, returns NIL.
1813 */
1814static inline void *set_item(struct file_ent_s *fent,
1815                    uint64_t offset,
1816                    uint16_t index)
1817{
1818        uint16_t free_blk = get_free_blk();
1819
1820        struct mem_table_s *mtbl = get_mtbl(fent->mtbl_blk, fent->mtbl_ent);
1821
1822        /* No Free Blocks Available */
1823        if(free_blk == NIL16)
1824        {
1825            evict_LRU(fent);
1826            free_blk = get_free_blk();
1827        }
1828       
1829        /* After Eviction Routine - No Free Blocks Available, Evict from mtbl
1830         * with the most memory entries
1831         */
1832        if(free_blk == NIL16)   
1833        {
1834            struct file_ent_s *max_fent = 0;
1835            struct mem_table_s *max_mtbl;
1836            int ment_count = 0;
1837            ment_count = locate_max_fent(&max_fent);
1838            max_mtbl = get_mtbl(max_fent->mtbl_blk, max_fent->mtbl_ent);
1839            if(ment_count == 0 || max_mtbl->lru_last == NIL16)
1840            {
1841                goto errout;
1842            }
1843            evict_LRU(max_fent);
1844            free_blk = get_free_blk();
1845        }
1846        /* TODO: other policy? */
1847
1848
1849        /* A Free Block is Avaiable for Use */
1850        if(free_blk != NIL16)
1851        {
1852            mtbl->num_blocks++;
1853            update_LRU(mtbl, index);
1854            /* set item to block number */
1855            mtbl->mem[index].tag = offset;
1856            mtbl->mem[index].item = free_blk;
1857            /* add block index to head of dirty list */
1858            mtbl->mem[index].dirty_next = mtbl->dirty_list;
1859            mtbl->dirty_list = index;
1860            /* Return the address of the block where data is stored */
1861            return (void *)&(ucache->b[free_blk]);
1862        }
1863errout:
1864    return (void *)(NIL);
1865}
1866
1867/**
1868 * Requests a location in memory to place the data identified by the mtbl and
1869 * offset parameters. Also inserts the necessary info into the mtbl.
1870 *
1871 */
1872static inline void *insert_mem(struct file_ent_s *fent, uint64_t offset,
1873                                              uint16_t *block_ndx)
1874{
1875    void* rc = 0;
1876    struct mem_table_s *mtbl = get_mtbl(fent->mtbl_blk, fent->mtbl_ent);
1877
1878    /* Lookup first */
1879    void *returnValue = lookup_mem(mtbl, offset, block_ndx, NULL, NULL);
1880    if(returnValue != (void *)NIL)
1881    {
1882        /* Already exists in mtbl so just return a ptr to the blk */
1883        return returnValue;
1884    }
1885
1886    /* Index into mem hash table */
1887    /* Hash to a bucket */
1888    uint16_t index = (uint16_t) ((offset / CACHE_BLOCK_SIZE) % MEM_TABLE_HASH_MAX);
1889
1890    int evict_rc = 0;
1891    uint16_t mentIndex = get_free_ment(mtbl);
1892    if(mentIndex == NIL16)
1893    {   /* No free ment available, so attempt eviction, and try again */
1894        evict_rc = evict_LRU(fent);
1895        mentIndex = get_free_ment(mtbl);
1896    }
1897
1898    /* Eviction Failed */
1899    if(mentIndex == NIL16)
1900    {
1901        return (void *)NULL;
1902    }
1903
1904    /* Procede with memory insertion if ment aquired */
1905    uint16_t next_ment = NIL16;
1906    /* Insert at head, keeping track of the previous head */
1907    next_ment = mtbl->bucket[index];
1908    mtbl->bucket[index] = mentIndex;
1909
1910    rc = set_item(fent, offset, mentIndex);
1911    if(rc != (void *)NIL)
1912    {
1913        mtbl->mem[mentIndex].next = next_ment;
1914        *block_ndx = mtbl->mem[mentIndex].item;
1915        return rc;     
1916    }
1917    else
1918    {
1919        /* Restore the previous head back to head of the chain */
1920        mtbl->bucket[index] = next_ment;
1921        return (void *)NIL;   
1922    }
1923}
1924
1925/**
1926 * Removes all table info regarding the block identified by the mtbl and
1927 * offset provided the block isn't locked.
1928 *
1929 * Flushing the block to fs now occurs here upon removal from cache.
1930 *
1931 * On success returns 1, on failure returns 0.
1932 *
1933 */
1934static int remove_mem(struct file_ent_s *fent, uint64_t offset)
1935{
1936    struct mem_table_s *mtbl = get_mtbl(fent->mtbl_blk, fent->mtbl_ent);
1937
1938    /* Some Indices */
1939    uint16_t item_index = NIL16; /* index of cached block */
1940    uint16_t mem_ent_index = NIL16;
1941    uint16_t mem_ent_prev_index = NIL16;
1942
1943    void *retValue = lookup_mem(mtbl, offset, &item_index, &mem_ent_index,
1944                                                     &mem_ent_prev_index);
1945    /* Verify we've recieved the necessary info */
1946    if(retValue == (void *)NIL)
1947    {
1948        return 0;
1949    }
1950
1951    /* Verify the block isn't being used by trying the corresponding lock */
1952    ucache_lock_t *block_lock = get_lock(mtbl->mem[mem_ent_index].item);
1953    int rc = lock_trylock(block_lock);
1954    if(rc != 0)
1955    {
1956        return -1;
1957    }
1958
1959    /* Aquire Lock */
1960    lock_lock(block_lock);
1961
1962    /* Optionally flush block - may need to be mandatory */
1963    flush_block(fent, &(mtbl->mem[mem_ent_index]));
1964
1965    /* Update First and Last...First */
1966    if(mem_ent_index == mtbl->lru_first)
1967    {
1968        /* Node is the head */
1969        mtbl->lru_first = mtbl->mem[mem_ent_index].lru_next;
1970    }
1971    if(mem_ent_index == mtbl->lru_last)
1972    {
1973        /* Node is the tail */
1974        mtbl->lru_last = mtbl->mem[mem_ent_index].lru_prev;
1975    }
1976   
1977    /* Remove from LRU */
1978    /* Update each of the adjacent nodes' link */
1979    uint16_t lru_prev = mtbl->mem[mem_ent_index].lru_prev;
1980    if(lru_prev != NIL16)
1981    {
1982        mtbl->mem[lru_prev].lru_next = mtbl->mem[mem_ent_index].lru_next;
1983    }
1984    uint16_t lru_next = mtbl->mem[mem_ent_index].lru_next;
1985    if(lru_next != NIL16)
1986    {
1987        mtbl->mem[lru_next].lru_prev = mtbl->mem[mem_ent_index].lru_prev;
1988    }
1989
1990    /* Add memory block back to free list */
1991    put_free_blk(item_index);
1992
1993    /* Repair link */
1994    if(mem_ent_prev_index != NIL16)
1995    {
1996        mtbl->mem[mem_ent_prev_index].next = mtbl->mem[mem_ent_index].next;
1997    }
1998
1999    /* Newly free mem entry becomes new head of free mem entry list if index
2000     * is less than hash table max
2001     */
2002    put_free_ment(mtbl, mem_ent_index);
2003    mtbl->num_blocks--;
2004
2005    /* Release Lock */
2006    lock_unlock(block_lock);
2007    return 1;
2008}
2009
2010/* The following two functions are provided for error checking purposes. */
2011/**
2012 * Prints the Least Recently Used (LRU) list.
2013 */
2014void print_LRU(struct mem_table_s *mtbl)
2015{
2016    fprintf(out, "\tprinting lru list:\n");
2017    fprintf(out, "\t\tmru: %hu\n", mtbl->lru_first);
2018    fprintf(out, "\t\t\tmru->lru_prev = %hu\n\t\t\tmru->lru_next = %hu\n",
2019        mtbl->mem[mtbl->lru_first].lru_prev, mtbl->mem[mtbl->lru_first].lru_next);
2020    uint16_t current = mtbl->mem[mtbl->lru_first].lru_next;
2021    while(current != mtbl->lru_last && current != NIL16)
2022    {
2023        fprintf(out, "\t\t\tcurr->lru_prev = %hu\n",
2024                       mtbl->mem[current].lru_prev);
2025        fprintf(out, "\t\t%hu\n", current);
2026        fprintf(out, "\t\t\tcurr->lru_next = %hu\n",
2027                       mtbl->mem[current].lru_next);
2028        current = mtbl->mem[current].lru_next;
2029    }
2030    fprintf(out, "\t\tlru: %hu\n", mtbl->lru_last);
2031    fprintf(out, "\t\t\tlru->lru_prev = %hu\n\t\t\tlru->lru_next = %hu\n",
2032        mtbl->mem[mtbl->lru_last].lru_prev, mtbl->mem[mtbl->lru_last].lru_next);
2033}
2034
2035/**
2036 * Prints the list of dirty (modified) blocks that should eventually be
2037 * flushed to disk.
2038 */
2039void print_dirty(struct mem_table_s *mtbl)
2040{
2041    fprintf(out, "\tprinting dirty list:\n");
2042    int i;
2043    for(i = 0; !dirty_done(i); i = dirty_next(mtbl, i))
2044    {
2045        fprintf(out, "\t\tment index = %hu\t\t\tdirty_next = %hu\n",
2046                                            i, dirty_next(mtbl, i));
2047    }
2048    if(i >= MEM_TABLE_ENTRY_COUNT && i != NIL16)
2049    {
2050        fprintf(out, "BAD MEM_TABLE_ENTRY INDEX: %hu\n", i);
2051        exit(0);
2052    }
2053    fprintf(out, "\t\tdone w/ dirty list\n");
2054}
2055
2056/*  End of Internal Only Functions    */
2057#endif /* PVFS_UCACHE_ENABLE */
2058
2059/*
2060 * Local variables:
2061 *  c-indent-level: 4
2062 *  c-basic-offset: 4
2063 * End:
2064 *
2065 * vim: ts=8 sts=4 sw=4 expandtab
2066 */
Note: See TracBrowser for help on using the browser.