pfile.c
"QuickCall" is the main engine for QuickATM's airport information kiosks. It is written entirely in C, and runs under DOS. Most of the data files are flattened versions of our relational database, which were dumped into a pseudo-filesystem that we created. "QuickCall" ran in over one hundred kiosks nationwide.
In the code, the macro
PUB_EXE is used to denote public functions, and
PVT_EXE indicates a private (module level) function.
These macros allow for quick greps and extractions of public and
private function names.
This module defines a caching frontend for all file access. It is so complex because files can come either from the real filesystem, from any of several pseudo-filesystems ('cat' files), or from the extended-filesystem (virtual files that are constructed in memory).
/* -- pfile.c: pf filesystem and the cache. -- */
/* -- Copyright (c) 1995 by QuickATM. All rights reserved. -- */
/*
*
* prefix notation:
* mf_: multi filesystem (real files located in the path pf->path).
* cf_: cat filesystem (virtual files indexed in indexind.ind).
* xf_: extended filesystem (contained in xf\xfile.c).
* pf_: pseudo filesystem (encompasses all of the above).
*
*/
#include <malloc.h>
#include "proto.h"
#include "d32.h"
static int max_cache_size = 0;
/* ----------------------------------------------------------------------- */
/* ----------------------------------------------------------------------- */
/* hits needed to remain in the cache after a level X clear( ). */
static int hits_needed[ ] = { 1, 2, 5, 10, 20, 50, 0 };
/* ----------------------------------------------------------------------- */
/* ----------------------------------------------------------------------- */
extern boolean mf_exists( char *file ); /* file.c */
extern boolean cf_exists( char *file ); /* file.c */
extern boolean xf_exists( char *file ); /* xf\xfile.c */
extern char *mf_load( char *name, int *size ); /* file.c */
extern char *cf_load( char* name, int *size ); /* file.c */
extern char *xf_load( char* name, int *size ); /* xf\xfile.c */
extern void mf_initialize( ); /* file.c */
extern void cf_initialize( ); /* file.c */
extern void mf_terminate( ); /* file.c */
extern void cf_terminate( ); /* file.c */
/* ----------------------------------------------------------------------- */
/* ----------------------------------------------------------------------- */
typedef struct _entry {
pfile *pf;
uint hits;
struct _entry *prev;
struct _entry *next;
} entry;
entry *head = NULL;
entry *tail = NULL;
static struct {
uint entries;
uint size;
uint hits;
uint misses;
} stats;
/* ----------------------------------------------------------------------- */
/* ----------------------------------------------------------------------- */
PUB_EXE void dump_cache_info( )
{
entry *en;
error( "%-20s cache has %d entries occupying %d bytes.\n",
"dump_cache_info:", stats.entries, stats.size );
error( "%-20s cache max size: %d bytes.\n",
"dump_cache_info:", max_cache_size );
error( "%-20s cache has had %d hits and %d misses.\n",
"dump_cache_info:", stats.hits, stats.misses );
for ( en = head; en != NULL; en = en->next ) {
error( "%-20s %3d hits on %6d byte file '%s'%s.\n",
"dump_cache_info:", en->hits, en->pf->len, en->pf->name,
( en->pf->in_use > 0 ) ? " (IN USE)" : "" );
}
}
/* ----------------------------------------------------------------------- */
/* ----------------------------------------------------------------------- */
PVT_EXE void move_to_front( entry *en )
{
entry *p, *n;
if ( head == en ) return;
/* link the chain around this entry (removing it from the chain). */
p = en->prev;
n = en->next;
if ( p != NULL ) p->next = n;
if ( n != NULL ) n->prev = p;
/* be careful not to lose the tail!! */
if ( tail == en ) tail = p;
/* put it at the head of the list. */
en->prev = NULL;
en->next = head;
head->prev = en;
head = en;
}
/* ----------------------------------------------------------------------- */
PVT_EXE pfile *locate( char *name )
{
entry *en;
int depth;
depth = 0;
for ( en = head; en != NULL; en = en->next ) {
depth++;
if ( strcmp( name, en->pf->name ) == 0 ) {
if ( en->pf->invalid == FALSE ) {
/* if this entry is deep, move it to the front. */
if ( depth > 10 ) move_to_front( en );
/* a hit! */
stats.hits++;
en->hits++;
return en->pf;
}
}
}
/* a miss. */
stats.misses++;
return NULL;
}
/* ----------------------------------------------------------------------- */
PVT_EXE void delete( entry *en )
{
entry *p, *n;
pfile *pf;
pf = en->pf;
/* can't delete files that are in use! */
if ( pf->in_use > 0 ) return;
/* make the list go around this entry. */
p = en->prev;
n = en->next;
if ( p != NULL ) p->next = n;
if ( n != NULL ) n->prev = p;
/* be careful not to lose the head or the tail!! */
if ( head == en ) head = n;
if ( tail == en ) tail = p;
/* update our statistics. */
stats.entries--;
stats.size -= pf->len;
if ( pf->name != NULL ) sfree( pf->name );
if ( pf->data != NULL ) sfree( pf->data );
sfree( pf );
sfree( en );
}
/* ----------------------------------------------------------------------- */
PVT_EXE void insert( pfile *pf )
{
entry *en;
/* create the new entry. */
en = smalloc( sizeof( entry ), "entry" );
en->pf = pf;
en->hits = 0;
/* note that this pfile is in the cache. */
pf->in_cache = TRUE;
/* put it at the head of the list. */
en->prev = NULL;
en->next = head;
head->prev = en;
head = en;
/* if this is the first entry we need to set 'tail' up. */
if ( tail == NULL ) tail = en;
/* update our statistics. */
stats.entries++;
stats.size += pf->len;
}
/* ----------------------------------------------------------------------- */
PVT_EXE void clear( int need )
{
int level;
entry *en, *next;
level = 0;
while ( hits_needed[ level ] > 0 ) {
/* free up some room in the cache. */
en = tail;
while ( en != NULL ) {
next = en->prev;
if ( en->hits < hits_needed[ level ] ) {
/* free this entry. */
delete( en );
/* cleared out enough? good. */
if ( stats.size < need ) return;
}
en = next;
}
level++;
}
}
/* ----------------------------------------------------------------------- */
PVT_EXE boolean can_insert( pfile *pf )
{
uint need;
/* HUGE files never get cached. */
if ( pf->len > 500000 ) return FALSE;
if ( max_cache_size == 0 ) return FALSE;
/* for really lame reasons, QRail routing ("rte\\...") CANNOT be cached. */
if ( strncmp( pf->name, "rte\\", 4 ) == 0 ) return FALSE;
/* how much space do we need? */
need = max_cache_size - pf->len;
/* if we have that much, we can add this file to the cache. */
if ( stats.size < need ) return TRUE;
/* clear out some files. */
clear( need );
return ( stats.size < need ) ? TRUE : FALSE;
}
/* ----------------------------------------------------------------------- */
/* ----------------------------------------------------------------------- */
PUB_EXE boolean pf_exists( char *file )
{
if ( ( file == NULL ) || ( *file == '\0' ) ) return FALSE;
/* if it's cached, it's there. */
if ( locate( file ) == TRUE ) return TRUE;
/* check out all the filesystems. */
if ( mf_exists( file ) == TRUE ) return TRUE;
if ( cf_exists( file ) == TRUE ) return TRUE;
if ( xf_exists( file ) == TRUE ) return TRUE;
return FALSE;
}
/* ----------------------------------------------------------------------- */
PUB_EXE pfile *pf_load( char *name )
{
pfile *pf = NULL;
char *buf = NULL;
char cachename[200];
int size;
if ( ( name == NULL ) || ( *name == '\0' ) ) return NULL;
/* convert the filename to a standard format. */
strcpy( cachename, name );
strlwr( cachename );
/* look for it in the cache. */
pf = locate( cachename );
if ( pf == NULL ) {
/* see if it exists in any filesystem -- try to load it. */
if ( buf == NULL ) buf = mf_load( name, &size );
if ( buf == NULL ) buf = cf_load( name, &size );
if ( buf == NULL ) buf = xf_load( name, &size );
if ( buf != NULL ) {
/* create a new pfile to hold it. */
pf = smalloc( sizeof( pfile ), "pfile" );
pf->name = sstrdup( cachename );
pf->data = buf;
pf->len = size;
pf->in_use = 0;
pf->invalid = pf->in_cache = FALSE;
/* try to cache this new pfile. */
if ( can_insert( pf ) == TRUE ) {
insert( pf );
}
}
}
#ifdef DEBUG
debug( D_FILE, "%-20s %s opening '%s'\n", "pf_load_buf()",
( pf == NULL ) ? "failure" : "success", name );
#endif
/* mark this file as in use (so we don't free it). */
if ( pf != NULL ) {
pf->in_use++;
}
return pf;
}
/* ----------------------------------------------------------------------- */
PUB_EXE void pf_free( pfile* pf )
{
if ( pf == NULL ) return;
if ( pf->in_use == 0 ) {
error( "%-20s attempt to free unused pfile '%s'\n",
"pf_free:", pf->name );
}
pf->in_use--;
if ( pf->in_cache == FALSE ) {
/* just free it -- it was never in a cache. */
/* note that non-cache items can NEVER be in use twice! */
if ( pf->name != NULL ) sfree( pf->name );
if ( pf->data != NULL ) sfree( pf->data );
sfree( pf );
}
}
/* ----------------------------------------------------------------------- */
/* ----------------------------------------------------------------------- */
/* pf_terminate: deallocates the pseudo filesystem structures. */
PUB_EXE void pf_terminate ()
{
entry *en, *next;
/* terminate the real filesystems. */
cf_terminate( );
mf_terminate( );
/* and clear the cache. */
en = head;
while ( en != NULL ) {
next = en->next;
delete( en );
en = next;
}
/* clear the statistics, if the cache is clean. */
if ( stats.entries == 0 ) {
stats.hits = stats.misses = 0;
}
}
/* ----------------------------------------------------------------------- */
/* ----------------------------------------------------------------------- */
/* pf_initialize: sets up the pseudo filesystem. */
PUB_EXE void pf_initialize( )
{
char *tmpbuf;
int max = 0;
boolean good = TRUE;
pf_terminate( );
mf_initialize( );
cf_initialize( );
/* see how much free memory we have for a cache. */
/* don't use all available memory... leave >8M free. */
while ( good == TRUE ) {
max += 1000000;
tmpbuf = malloc( max );
if ( tmpbuf != NULL ) {
free( tmpbuf );
} else {
good = FALSE;
}
}
max_cache_size = max - 8000000;
if ( max_cache_size < 0 ) max_cache_size = 0;
if ( max_cache_size > 8000000 ) max_cache_size = 8000000;
}
/* ----------------------------------------------------------------------- */
/* ----------------------------------------------------------------------- */
Source code is:
© Copyright 1996, QuickATM. All rights reserved.