/* Copyright 1999 Element 14 Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
*
*  NameCache.c  -- Long filename cacheing using circular buffer
*
*  05-02-99 sbrodie  Original
*/

/* Test program builds with:
 *
 *     cc -ITCPIPLibs: -DLONGNAMES -DTEST -Wpc NameCache.c
 *
 */

/* This file implements a circular buffer for temporarily storing search records
 * from a search operation.  The directory name and the returned block of data
 * are stored and returned if the data for the same object is requested again.
 * The cache is flushed on exit from each FileSwitch entry point to ensure
 * cache consistency.
 *
 * Each record is word-aligned and starts with a rec_hdr structure.  The dirname
 * char array is variable length and is rounded up to make the structure an exact
 * number of words long.  This is followed immediately by the variable length record
 * returned by the server (23 bytes header, plus the leafname).  Again, the size of
 * this is rounded up to the next word boundary.  A complete record is never split
 * over the end of the buffer.  The rec_hdr.offset_next value is the offset from the
 * start of the rec_hdr to the start of the next rec_hdr.  The value is in bytes.
 * The value may be negative if the next entry starts at the beginning of the buffer.
 * The rec_hdr.offset_data value is -1 if the record does not hold a valid entry.
 * NameCache.last is -1 if there are no valid records at all, otherwise it is the
 * offset from the start of the buffer to the most recently added item (ie. the
 * one whose offset_next gives the location of NameCache.head.  This is used for
 * maintaining the chain of records.
 */

/* Standard includes */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <ctype.h>
#include "kernel.h"

/* Our includes */
#include "stdtypes.h"
#include "NameCache.h"

#ifndef LONGNAMES
BYTE *NameCache_Locate(const char *filename)
{
  (void) filename;
  return NULL;
}

void NameCache_Add(const char *dirname, BYTE *b)
{
  (void) dirname;
  (void) b;
}

void NameCache_Flush(ncf_reason why)
{
        (void) why;
}

void NameCache_Init(void)
{
}
#else

static struct {
        BYTE *data;
        int  head;
        int  tail;
        int  last;
        int  hits;
} NameCache;

typedef struct {
        int  offset_next;
        int  offset_data;
        char dirbase[1];
} rec_hdr;

static const size_t cachesize = 4096;

static void NameCache_Exit(void)
{
        free(NameCache.data);
        NameCache.data = NULL;
}

void NameCache_Flush(ncf_reason why)
{
        debug2("NameCacheFlush(reason = %d) hits %d\n", why, NameCache.hits);
        if (why == ncf_REINIT) NameCache.hits = 0;
        if (NameCache.data) {
                NameCache.head = 0;
                NameCache.tail = 0;
                NameCache.last = -1;
                ((rec_hdr *) NameCache.data) -> offset_next = -1;
        }
}

void NameCache_Init(void)
{
        NameCache.data = calloc(1, cachesize);
        if (NameCache.data != 0) {
        	atexit(NameCache_Exit);
        }
       	NameCache_Flush(ncf_REINIT);
}

BYTE *NameCache_Locate(const char *filename)
{
        rec_hdr *hdr;
	int offset = NameCache.tail;
	if (NameCache.data == NULL || NameCache.last == -1) return NULL;

	for (;;offset += hdr->offset_next) {
	        hdr = (rec_hdr *)(NameCache.data + offset);
	        if (hdr->offset_data == -1) {
	                if (offset == NameCache.head) break;
	        }
	        else {
	        	BYTE *res = NameCache.data + offset + hdr->offset_data;
        		const size_t baselen = strlen(hdr->dirbase);

        		if (filename[baselen-1] == '\\')
	                if (!strcmp((char *)res+23, filename + baselen))
	        	if (!strncmp(hdr->dirbase, filename, baselen)) {
	                        /* Found match */
	                        debug1("NameCache: returning match for `%s'\n", filename);
	                        ++NameCache.hits;
        	                return res;
                	}
	        }
	}

	return NULL;
}

static void NameCache_Trim(void)
{
	/* Any deleted entries at tail of queue - dump them */
        while (NameCache.tail != NameCache.head) {
	        rec_hdr *tailp = (rec_hdr *) (NameCache.data + NameCache.tail);
                if (tailp->offset_data != -1) {
                	break;
                }
                NameCache.tail += tailp->offset_next;
        }

        if (NameCache.tail == NameCache.head) {
                /* Empty */
                debug0("Reset cache\n");
                NameCache.last = -1;
                NameCache.tail = NameCache.head = 0;
                ((rec_hdr *) (NameCache.data))->offset_data = -1;
        }
}

void NameCache_Add(const char *dirname, BYTE *b)
{
        /* Calculate size of record data, directory name, and
         * management overhead.
         */
        static const size_t overhead = offsetof(rec_hdr, dirbase);
        const size_t filename_len = b[22];
        const size_t dirname_len = strlen(dirname) + 1;
        const size_t rec_size = ((23 + filename_len + 1 + 3) & ~3) +
        			(2 * overhead) +
        			((dirname_len + 3 & ~3));
        rec_hdr *new_rec;

	if (NameCache.data == NULL || rec_size >= cachesize) return;

        if ((NameCache.head + rec_size) > cachesize) {
                /* Re-use first record as we will require a wrap-around */
                rec_hdr *tailp;
                NameCache.tail = 0;
                while (NameCache.tail < rec_size) {
                	tailp = (rec_hdr *) (NameCache.data + NameCache.tail);
                	if (NameCache.tail == NameCache.head) {
                                /* NameCache_Trim() will sort this out */
                                break;
                        }
                	NameCache.tail += tailp->offset_next;
                }
                debug2("Tail now %#x ; Head now %#x\n", NameCache.tail, NameCache.head);
                if (NameCache.head != NameCache.tail) {
                        /* If they are equal, NameCache_Trim() will reset the queue */
                        NameCache.head = 0;
                }
        }
        else if ((NameCache.tail < NameCache.head) || NameCache.last == -1) {
                /* OK - we haven't got a loop active */
        }
        else while ((NameCache.tail - NameCache.head) < rec_size) {
                /* Buffer is already wrapped, but we can afford to wrap it */
               	rec_hdr *tailp = (rec_hdr *) (NameCache.data + NameCache.tail);
               	debug1("Discarding entry at %#x\n", NameCache.tail);
                NameCache.tail += tailp->offset_next;
                if (NameCache.tail <= NameCache.head) {
                        /* Have wrapped around */
                        break;
                }
        }

	NameCache_Trim();

	/* Locate insertion point and add onto queue */
	if (NameCache.last != -1) {
	        rec_hdr *lastp = (rec_hdr *) (NameCache.data + NameCache.last);
	        lastp->offset_next = NameCache.head - NameCache.last;
	}

	debug2("Adding `%s' in `%s'\n", (char *)b+23, dirname);
	NameCache.last = NameCache.head;
	NameCache.head = NameCache.head + rec_size - overhead;
	new_rec = (rec_hdr *) (NameCache.data + NameCache.head);
	new_rec->offset_data = -1;
	new_rec->offset_next = 0;
	new_rec = (rec_hdr *) (NameCache.data + NameCache.last);
	new_rec->offset_data = overhead + ((dirname_len + 3) & ~3);
	new_rec->offset_next = NameCache.head - NameCache.last;
	memcpy(new_rec->dirbase, dirname, dirname_len);
	memcpy(NameCache.data + NameCache.last + new_rec->offset_data, b,
		23 + filename_len);
	NameCache.data[NameCache.last + new_rec->offset_data + 23 + filename_len] = '\0';
}

#ifdef TEST

static void DumpBuffer(void *ptr, int len)
{
        const char *membuf = ptr;
        int i,j;
        for (i=0; i<((len+31)&~31); ++i) {
                if (!(i & 31)) {
                        printf("  ");
                        if (i) for (j = i - 32; j != i; ++j) {
                                printf("%c", (membuf[j]>=32 && membuf[j] != 0x7f) ?
                                membuf[j] : '.');
                        }
                        printf("\n%04x: ", i);
                }
                if (i>=len) {
                        printf("  ");
                        if (3==(i & 3)) printf(" ");
                }
                else {
                        printf("%02x", membuf[i]);
                        if (3==(i & 3)) printf(" ");
                }
        }
        if (i) for (printf("  "), j = i - 32; j != i; ++j) printf("%c",
            j>=len ? ' ' : (membuf[j]>=32 && membuf[j] != 0x7f) ?
            membuf[j] : '.');
	printf("\n");
}

static void DumpNameCache(void)
{
        printf("head = %#x\ntail = %#x\nlast = %#x\nData = %p",
        	NameCache.head, NameCache.tail, NameCache.last, NameCache.data);
        DumpBuffer(NameCache.data, cachesize);
}

static void SoakTest(void)
{
        int i, j;
        BYTE entry[256], *res;
        char dirname[256];
        char fullname[256];
        NameCache_Init();

	/* Force repeatability */
	srand(1);

        memset(entry, 7, sizeof(entry));

	for (i=0;i<500000;++i) {
	        int dlen = (rand() % 32) + 1;
	        int llen = (rand() % 64) + 1;
	        if (!(i % 256)) printf("%8d", i);
	        for (j=0; j<(dlen+llen); ++j) {
	                dirname[j] = (rand() % 26) + 'A';
	                entry[23 + j] = (rand() % 26) + 'a';
	        }
	        entry[22] = llen;
	        dirname[dlen] = '\0';
	        //printf("Add `%s' `%.*s'\n", dirname, llen, (char *)entry+23);
	        NameCache_Add(0, dirname, entry);
	        sprintf(fullname, "%s\\%.*s", dirname, llen, (char *)entry+23);
	        //printf("Seeking `%s'\n", fullname);
	        res = NameCache_Locate(fullname, 0);
	        if (!res) {
	                DumpNameCache();
	                printf("\n\n(%d) Failed to find entry!\n", i);
	                exit(1);
	        }
	}
	printf("\n");
}

int main(void)
{
	BYTE entry[256], *res;
	char fullname[64], *const leafptr = (char *) entry+23;
	char *dirname = "A:\\ADIRECTORY";
	char *leafname = "filen";

	sprintf(fullname, "%s\\%s", dirname, leafname);
	res = NameCache_Locate(fullname, 0);
	printf("Lookup returned %p (%#08x)\n", res, res - NameCache.data);

	memset(entry, 4, sizeof(entry));
	entry[22] = strlen(leafname);
	strcpy(leafptr, leafname);

	NameCache_Init();
	DumpNameCache();
	NameCache_Add(0, dirname, entry);
	dirname = "A:\\2NDDIRNAME";
	NameCache_Add(0, dirname, entry);
	dirname = "A:\\3RDDIRNAME";
	NameCache_Add(0, dirname, entry);
	//DumpNameCache();
	dirname = "B:\\BDIRNAME";
	NameCache_Add(0, dirname, entry);
	NameCache_Add(0, dirname, entry);
	DumpNameCache();
	dirname = "C:\\ANOTHERCDIR";
	NameCache_Add(0, dirname, entry);
	DumpNameCache();
	leafname = "a very long filename generated to cause trouble"\
	 "as several items should be thrown away and cause a cache reset to occur?";
	entry[22] = strlen(leafname);
	strcpy(leafptr, leafname);
	DumpNameCache();
	NameCache_Add(0, dirname, entry);
	DumpNameCache();

	sprintf(fullname, "%s\\%s", dirname, leafname);
	res = NameCache_Locate(fullname, 0);
	printf("Lookup returned %p (%#08x)\n", res, res - NameCache.data);

	SoakTest();

        return 0;
}
#endif /* TEST */
#endif /* LONGNAMES */