/*
- * $RCSId: xc/lib/fontconfig/src/fccache.c,v 1.12 2002/08/22 07:36:44 keithp Exp $
- *
* Copyright © 2000 Keith Packard
* Copyright © 2005 Patrick Lam
*
# include <windows.h>
#endif
-#define ENDIAN_TEST 0x12345678
-#define MACHINE_SIGNATURE_SIZE (9 + 5*20 + 1)
-/* for when we don't have sysconf: */
-#define FC_HARDCODED_PAGESIZE 8192
-
#ifndef O_BINARY
#define O_BINARY 0
#endif
-static FILE *
-FcDirCacheOpen (FcConfig *config, const FcChar8 *dir, FcChar8 **cache_path);
-
-static void *
-FcDirCacheProduce (FcFontSet *set, FcCache * metadata);
-
-static off_t
-FcCacheNextOffset(off_t w);
-
-static FcBool
-FcCacheHaveBank (int bank);
-
-static void
-FcCacheAddBankDir (int bank, const char * dir);
-
struct MD5Context {
FcChar32 buf[4];
FcChar32 bits[2];
static void MD5Final(unsigned char digest[16], struct MD5Context *ctx);
static void MD5Transform(FcChar32 buf[4], FcChar32 in[16]);
-#define FC_DBG_CACHE_REF 1024
-
-static char *
-FcCacheReadString (FILE *file, char *dest, int len)
-{
- int c;
- char *d = dest;
-
- if (len == 0)
- return 0;
-
- while ((c = getc (file)) != EOF && len > 0) {
- *d++ = c;
- if (c == '\0')
- return dest;
- len--;
- }
- return 0;
-}
-
-static FcBool
-FcCacheWriteString (int fd, const char *chars)
-{
- if (write (fd, chars, strlen(chars)+1) != strlen(chars)+1)
- return FcFalse;
- return FcTrue;
-}
-
-/*
- * Find the next presumably-mmapable offset after the supplied file
- * position.
- */
-static off_t
-FcCacheNextOffset(off_t w)
-{
- static long pagesize = -1;
-
- if (w == -1)
- return w;
-
- if (pagesize == -1)
-#if defined (HAVE_SYSCONF)
- pagesize = sysconf(_SC_PAGESIZE);
-#else
- pagesize = FC_HARDCODED_PAGESIZE;
-#endif
- if (w % pagesize == 0)
- return w;
- else
- return ((w / pagesize)+1)*pagesize;
-}
-
-/* Does not check that the cache has the appropriate arch section. */
-FcBool
-FcDirCacheValid (const FcChar8 *dir, FcConfig *config)
-{
- FILE *file;
-
- file = FcDirCacheOpen (config, dir, NULL);
-
- if (file != NULL)
- return FcFalse;
- fclose (file);
-
- return FcTrue;
-}
-
#define CACHEBASE_LEN (1 + 32 + 1 + sizeof (FC_ARCHITECTURE) + sizeof (FC_CACHE_SUFFIX))
static const char bin2hex[] = { '0', '1', '2', '3',
}
static int
-FcCacheReadDirs (FcConfig * config,
- FcStrList *list, FcFontSet * set, FcStrSet *processed_dirs)
+FcDirCacheOpenFile (const FcChar8 *cache_file, struct stat *file_stat)
{
- int ret = 0;
- FcChar8 *dir;
- FcStrSet *subdirs;
- FcStrList *sublist;
+ int fd;
- /*
- * Read in the results from 'list'.
- */
- while ((dir = FcStrListNext (list)))
+ fd = open((char *) cache_file, O_RDONLY | O_BINARY);
+ if (fd < 0)
+ return fd;
+ if (fstat (fd, file_stat) < 0)
{
- if (!FcConfigAcceptFilename (config, dir))
- continue;
-
- /* Skip this directory if already updated
- * to avoid the looped directories via symlinks
- * Clearly a dir not in fonts.conf shouldn't be globally cached.
- */
-
- if (FcStrSetMember (processed_dirs, dir))
- continue;
- if (!FcStrSetAdd (processed_dirs, dir))
- continue;
-
- subdirs = FcStrSetCreate ();
- if (!subdirs)
- {
- fprintf (stderr, "Can't create directory set\n");
- ret++;
- continue;
- }
-
- FcDirScanConfig (set, subdirs,
- config->blanks, dir, FcFalse, config);
-
- sublist = FcStrListCreate (subdirs);
- FcStrSetDestroy (subdirs);
- if (!sublist)
- {
- fprintf (stderr, "Can't create subdir list in \"%s\"\n", dir);
- ret++;
- continue;
- }
- ret += FcCacheReadDirs (config, sublist, set, processed_dirs);
+ close (fd);
+ return -1;
}
- FcStrListDone (list);
- return ret;
-}
-
-FcFontSet *
-FcCacheRead (FcConfig *config)
-{
- FcFontSet *s = FcFontSetCreate();
- FcStrSet *processed_dirs;
-
- if (!s)
- return 0;
-
- processed_dirs = FcStrSetCreate();
- if (!processed_dirs)
- goto bail;
-
- if (FcCacheReadDirs (config, FcConfigGetConfigDirs (config), s, processed_dirs))
- goto bail1;
-
- FcStrSetDestroy (processed_dirs);
- return s;
-
- bail1:
- FcStrSetDestroy (processed_dirs);
- bail:
- FcFontSetDestroy (s);
- return 0;
+ return fd;
}
-/* Opens the cache file for 'dir' for reading.
- * This searches the list of cache dirs for the relevant cache file,
- * returning the first one found.
+/*
+ * Look for a cache file for the specified dir. Attempt
+ * to use each one we find, stopping when the callback
+ * indicates success
*/
-static FILE *
-FcDirCacheOpen (FcConfig *config, const FcChar8 *dir, FcChar8 **cache_path)
+static FcBool
+FcDirCacheProcess (FcConfig *config, const FcChar8 *dir,
+ FcBool (*callback) (int fd, off_t size, void *closure),
+ void *closure, FcChar8 **cache_file_ret)
{
- FILE *file = NULL;
- FcChar8 *cache_hashed = NULL;
+ int fd = -1;
FcChar8 cache_base[CACHEBASE_LEN];
FcStrList *list;
FcChar8 *cache_dir;
struct stat file_stat, dir_stat;
+ FcBool ret = FcFalse;
if (stat ((char *) dir, &dir_stat) < 0)
- return NULL;
+ return FcFalse;
FcDirCacheBasename (dir, cache_base);
list = FcStrListCreate (config->cacheDirs);
if (!list)
- return NULL;
+ return FcFalse;
while ((cache_dir = FcStrListNext (list)))
{
- cache_hashed = FcStrPlus (cache_dir, cache_base);
+ FcChar8 *cache_hashed = FcStrPlus (cache_dir, cache_base);
if (!cache_hashed)
break;
- file = fopen((char *) cache_hashed, "rb");
- if (file != NULL) {
- if (fstat (fileno (file), &file_stat) >= 0 &&
- dir_stat.st_mtime <= file_stat.st_mtime)
+ fd = FcDirCacheOpenFile (cache_hashed, &file_stat);
+ if (fd >= 0) {
+ if (dir_stat.st_mtime <= file_stat.st_mtime)
{
- break;
+ ret = (*callback) (fd, file_stat.st_size, closure);
+ if (ret)
+ {
+ if (cache_file_ret)
+ *cache_file_ret = cache_hashed;
+ else
+ FcStrFree (cache_hashed);
+ close (fd);
+ break;
+ }
}
- fclose (file);
- file = NULL;
+ close (fd);
}
- FcStrFree (cache_hashed);
- cache_hashed = NULL;
+ FcStrFree (cache_hashed);
}
FcStrListDone (list);
-
- if (file == NULL)
- return NULL;
- if (cache_path)
- *cache_path = cache_hashed;
- else
- FcStrFree (cache_hashed);
-
- return file;
+ return ret;
}
-/* read serialized state from the cache file */
-FcBool
-FcDirCacheRead (FcFontSet * set, FcStrSet * dirs,
- const FcChar8 *dir, FcConfig *config)
-{
- FILE *file;
- FcCache metadata;
- void *current_dir_block;
- char subdir_name[FC_MAX_FILE_LEN + 1 + 12 + 1];
-
- file = FcDirCacheOpen (config, dir, NULL);
- if (file == NULL)
- goto bail;
-
- if (fread(&metadata, sizeof(FcCache), 1, file) != 1)
- goto bail1;
- if (metadata.magic != FC_CACHE_MAGIC)
- goto bail1;
+#define FC_CACHE_MIN_MMAP 1024
- while (FcCacheReadString (file, subdir_name, sizeof (subdir_name)) &&
- strlen (subdir_name) > 0)
- FcStrSetAdd (dirs, (FcChar8 *)subdir_name);
+/*
+ * Map a cache file into memory
+ */
+static FcCache *
+FcDirCacheMapFd (int fd, off_t size)
+{
+ FcCache *cache = NULL;
+ FcBool allocated = FcFalse;
- if (metadata.count)
+ if (size < sizeof (FcCache))
+ return NULL;
+ /*
+ * For small cache files, just read them into memory
+ */
+ if (size >= FC_CACHE_MIN_MMAP)
{
- int fd = fileno (file);
#if defined(HAVE_MMAP) || defined(__CYGWIN__)
- current_dir_block = mmap (0, metadata.count,
- PROT_READ, MAP_SHARED, fd, metadata.pos);
- if (current_dir_block == MAP_FAILED)
- goto bail1;
+ cache = mmap (0, size, PROT_READ, MAP_SHARED, fd, 0);
#elif defined(_WIN32)
{
HANDLE hFileMap;
- hFileMap = CreateFileMapping((HANDLE) _get_osfhandle(fd), NULL, PAGE_READONLY, 0, 0, NULL);
- if (hFileMap == NULL)
- goto bail1;
-
- current_dir_block = MapViewOfFile (hFileMap, FILE_MAP_READ, 0, metadata.pos, metadata.count);
- if (current_dir_block == NULL)
+ cache = NULL;
+ hFileMap = CreateFileMapping((HANDLE) _get_osfhandle(fd), NULL,
+ PAGE_READONLY, 0, 0, NULL);
+ if (hFileMap != NULL)
{
+ cache = MapViewOfFile (hFileMap, FILE_MAP_READ, 0, 0, size);
CloseHandle (hFileMap);
- goto bail1;
}
}
-#else
- if (lseek (fd, metatdata.pos, SEEK_SET) == -1)
- goto bail1;
-
- current_dir_block = malloc (metadata.count);
- if (!current_dir_block)
- goto bail1;
+#endif
+ }
+ if (!cache)
+ {
+ cache = malloc (size);
+ if (!cache)
+ return NULL;
- /* could also use CreateMappedViewOfFile under MinGW... */
- if (read (fd, current_dir_block, metadata.count) != metadata.count)
+ if (read (fd, cache, size) != size)
{
- free (current_dir_block);
- goto bail1;
+ free (cache);
+ return NULL;
}
+ allocated = FcTrue;
+ }
+ if (cache->magic != FC_CACHE_MAGIC_MMAP ||
+ cache->version < FC_CACHE_CONTENT_VERSION ||
+ cache->size != size)
+ {
+ if (allocated)
+ free (cache);
+ else
+ {
+#if defined(HAVE_MMAP) || defined(__CYGWIN__)
+ munmap (cache, size);
+#elif defined(_WIN32)
+ UnmapViewOfFile (cache);
#endif
- FcCacheAddBankDir (metadata.bank, (char *) dir);
- if (!FcFontSetUnserialize (&metadata, set, current_dir_block))
- goto bail1;
+ }
+ return NULL;
}
- if (config)
- FcConfigAddFontDir (config, (FcChar8 *)dir);
- fclose(file);
+ /* Mark allocated caches so they're freed rather than unmapped */
+ if (allocated)
+ cache->magic = FC_CACHE_MAGIC_ALLOC;
+
+ return cache;
+}
+
+void
+FcDirCacheUnload (FcCache *cache)
+{
+ switch (cache->magic) {
+ case FC_CACHE_MAGIC_ALLOC:
+ free (cache);
+ break;
+ case FC_CACHE_MAGIC_MMAP:
+#if defined(HAVE_MMAP) || defined(__CYGWIN__)
+ munmap (cache, cache->size);
+#elif defined(_WIN32)
+ UnmapViewOfFile (cache);
+#endif
+ break;
+ }
+}
+
+static FcBool
+FcDirCacheMapHelper (int fd, off_t size, void *closure)
+{
+ FcCache *cache = FcDirCacheMapFd (fd, size);
+
+ if (!cache)
+ return FcFalse;
+ *((FcCache **) closure) = cache;
return FcTrue;
+}
- bail1:
- fclose (file);
- bail:
- return FcFalse;
+FcCache *
+FcDirCacheLoad (const FcChar8 *dir, FcConfig *config, FcChar8 **cache_file)
+{
+ FcCache *cache = NULL;
+
+ if (!FcDirCacheProcess (config, dir,
+ FcDirCacheMapHelper,
+ &cache, cache_file))
+ return NULL;
+ return cache;
}
-static void *
-FcDirCacheProduce (FcFontSet *set, FcCache *metadata)
+FcCache *
+FcDirCacheLoadFile (const FcChar8 *cache_file, struct stat *file_stat)
{
- void * current_dir_block, * final_dir_block;
- static unsigned int rand_state = 0;
- int bank, needed_bytes_no_align;
+ int fd;
+ FcCache *cache;
-#if defined (HAVE_RAND_R)
- if (!rand_state)
- rand_state = time(0L);
- bank = rand_r(&rand_state);
+ fd = FcDirCacheOpenFile (cache_file, file_stat);
+ if (fd < 0)
+ return NULL;
+ cache = FcDirCacheMapFd (fd, file_stat->st_size);
+ close (fd);
+ return cache;
+}
- while (FcCacheHaveBank(bank))
- bank = rand_r(&rand_state);
-#else
- if (!rand_state)
- {
- rand_state = 1;
- srand (time (0L));
- }
- bank = rand();
+/*
+ * Validate a cache file by reading the header and checking
+ * the magic number and the size field
+ */
+static FcBool
+FcDirCacheValidateHelper (int fd, off_t size, void *closure)
+{
+ FcBool ret = FcTrue;
+ FcCache c;
+ struct stat file_stat;
+
+ if (read (fd, &c, sizeof (FcCache)) != sizeof (FcCache))
+ ret = FcFalse;
+ else if (c.magic != FC_CACHE_MAGIC_MMAP)
+ ret = FcFalse;
+ else if (c.version < FC_CACHE_CONTENT_VERSION)
+ ret = FcFalse;
+ else if (fstat (fd, &file_stat) < 0)
+ ret = FcFalse;
+ else if (file_stat.st_size != c.size)
+ ret = FcFalse;
+ return ret;
+}
- while (FcCacheHaveBank(bank))
- bank = rand();
-#endif
+static FcBool
+FcDirCacheValidConfig (const FcChar8 *dir, FcConfig *config)
+{
+ return FcDirCacheProcess (config, dir,
+ FcDirCacheValidateHelper,
+ NULL, NULL);
+}
+
+FcBool
+FcDirCacheValid (const FcChar8 *dir)
+{
+ FcConfig *config;
+
+ config = FcConfigGetCurrent ();
+ if (!config)
+ return FcFalse;
- memset (metadata, 0, sizeof(FcCache));
- FcFontSetNewBank();
- needed_bytes_no_align = FcFontSetNeededBytes (set);
- metadata->count = needed_bytes_no_align +
- FcFontSetNeededBytesAlign ();
- metadata->magic = FC_CACHE_MAGIC;
- metadata->bank = bank;
+ return FcDirCacheValidConfig (dir, config);
+}
- if (!needed_bytes_no_align) /* not a failure, no fonts to write */
- {
- /* no, you don't really need to write any bytes at all. */
- metadata->count = 0;
- return 0;
- }
+/*
+ * Build a cache structure from the given contents
+ */
+FcCache *
+FcDirCacheBuild (FcFontSet *set, const FcChar8 *dir, FcStrSet *dirs)
+{
+ FcSerialize *serialize = FcSerializeCreate ();
+ FcCache *cache;
+ int i;
+ intptr_t cache_offset;
+ intptr_t dirs_offset;
+ FcChar8 *dir_serialize;
+ intptr_t *dirs_serialize;
+ FcFontSet *set_serialize;
+
+ if (!serialize)
+ return NULL;
+ /*
+ * Space for cache structure
+ */
+ cache_offset = FcSerializeReserve (serialize, sizeof (FcCache));
+ /*
+ * Directory name
+ */
+ if (!FcStrSerializeAlloc (serialize, dir))
+ goto bail1;
+ /*
+ * Subdirs
+ */
+ dirs_offset = FcSerializeAlloc (serialize, dirs, dirs->num * sizeof (FcChar8 *));
+ for (i = 0; i < dirs->num; i++)
+ if (!FcStrSerializeAlloc (serialize, dirs->strs[i]))
+ goto bail1;
- current_dir_block = malloc (metadata->count);
- if (!current_dir_block)
- goto bail;
+ /*
+ * Patterns
+ */
+ if (!FcFontSetSerializeAlloc (serialize, set))
+ goto bail1;
+
+ /* Serialize layout complete. Now allocate space and fill it */
+ cache = malloc (serialize->size);
+ if (!cache)
+ goto bail1;
/* shut up valgrind */
- memset (current_dir_block, 0, metadata->count);
- final_dir_block = FcFontSetDistributeBytes (metadata, current_dir_block);
+ memset (cache, 0, serialize->size);
+
+ serialize->linear = cache;
+
+ cache->magic = FC_CACHE_MAGIC_ALLOC;
+ cache->version = FC_CACHE_CONTENT_VERSION;
+ cache->size = serialize->size;
- if ((void *)((char *)current_dir_block+metadata->count) < final_dir_block)
- goto bail;
-
- if (!FcFontSetSerialize (bank, set))
- goto bail;
+ /*
+ * Serialize directory name
+ */
+ dir_serialize = FcStrSerialize (serialize, dir);
+ if (!dir_serialize)
+ goto bail2;
+ cache->dir = FcPtrToOffset (cache, dir_serialize);
+
+ /*
+ * Serialize sub dirs
+ */
+ dirs_serialize = FcSerializePtr (serialize, dirs);
+ if (!dirs_serialize)
+ goto bail2;
+ cache->dirs = FcPtrToOffset (cache, dirs_serialize);
+ cache->dirs_count = dirs->num;
+ for (i = 0; i < dirs->num; i++)
+ {
+ FcChar8 *d_serialize = FcStrSerialize (serialize, dirs->strs[i]);
+ if (!d_serialize)
+ goto bail2;
+ dirs_serialize[i] = FcPtrToOffset (dirs_serialize, d_serialize);
+ }
+
+ /*
+ * Serialize font set
+ */
+ set_serialize = FcFontSetSerialize (serialize, set);
+ if (!set_serialize)
+ goto bail2;
+ cache->set = FcPtrToOffset (cache, set_serialize);
- return current_dir_block;
+ FcSerializeDestroy (serialize);
+
+ return cache;
- bail:
- free (current_dir_block);
- return 0;
+bail2:
+ free (cache);
+bail1:
+ FcSerializeDestroy (serialize);
+ return NULL;
}
static FcBool
/* write serialized state to the cache file */
FcBool
-FcDirCacheWrite (FcFontSet *set, FcStrSet *dirs, const FcChar8 *dir, FcConfig *config)
+FcDirCacheWrite (FcCache *cache, FcConfig *config)
{
+ FcChar8 *dir = FcCacheDir (cache);
FcChar8 cache_base[CACHEBASE_LEN];
FcChar8 *cache_hashed;
- int fd, i;
+ int fd;
FcAtomic *atomic;
- FcCache metadata;
- void *current_dir_block = 0;
FcStrList *list;
FcChar8 *cache_dir = NULL;
FcChar8 *test_dir;
- off_t header_len;
+ int magic;
+ int written;
/*
* Write it to the first directory in the list which is writable
FcStrListDone (list);
if (!cache_dir)
return FcFalse;
+
FcDirCacheBasename (dir, cache_base);
cache_hashed = FcStrPlus (cache_dir, cache_base);
if (!cache_hashed)
return FcFalse;
- current_dir_block = FcDirCacheProduce (set, &metadata);
-
- if (metadata.count && !current_dir_block)
- goto bail1;
-
if (FcDebug () & FC_DBG_CACHE)
printf ("FcDirCacheWriteDir dir \"%s\" file \"%s\"\n",
dir, cache_hashed);
goto bail1;
if (!FcAtomicLock (atomic))
- goto bail2;
+ goto bail3;
fd = open((char *)FcAtomicNewFile (atomic), O_RDWR | O_CREAT | O_BINARY, 0666);
if (fd == -1)
- goto bail3;
+ goto bail4;
+
+ /* Temporarily switch magic to MMAP while writing to file */
+ magic = cache->magic;
+ if (magic != FC_CACHE_MAGIC_MMAP)
+ cache->magic = FC_CACHE_MAGIC_MMAP;
/*
- * Compute file header length -- the FcCache followed by the subdir names
+ * Write cache contents to file
*/
- header_len = sizeof (FcCache);
- for (i = 0; i < dirs->size; i++)
- header_len += strlen ((char *)dirs->strs[i]) + 1;
+ written = write (fd, cache, cache->size);
- metadata.pos = FcCacheNextOffset (lseek (fd, 0, SEEK_CUR) + header_len);
- metadata.subdirs = dirs->size;
+ /* Switch magic back */
+ if (magic != FC_CACHE_MAGIC_MMAP)
+ cache->magic = magic;
- /*
- * Write out the header
- */
- if (write (fd, &metadata, sizeof(FcCache)) != sizeof(FcCache))
+ if (written != cache->size)
{
- perror("write metadata");
+ perror ("write cache");
goto bail5;
}
-
- for (i = 0; i < dirs->size; i++)
- FcCacheWriteString (fd, (char *)dirs->strs[i]);
-
- if (metadata.count)
- {
- if (lseek (fd, metadata.pos, SEEK_SET) != metadata.pos)
- perror("lseek");
- else if (write (fd, current_dir_block, metadata.count) !=
- metadata.count)
- perror("write current_dir_block");
- free (current_dir_block);
- current_dir_block = 0;
- }
close(fd);
if (!FcAtomicReplaceOrig(atomic))
- goto bail3;
- FcStrFree ((FcChar8 *)cache_hashed);
+ goto bail4;
+ FcStrFree (cache_hashed);
FcAtomicUnlock (atomic);
FcAtomicDestroy (atomic);
return FcTrue;
bail5:
close (fd);
- bail3:
+ bail4:
FcAtomicUnlock (atomic);
- bail2:
+ bail3:
FcAtomicDestroy (atomic);
bail1:
- FcStrFree ((FcChar8 *)cache_hashed);
- if (current_dir_block)
- free (current_dir_block);
- return FcFalse;
-}
-
-static int banks_ptr = 0, banks_alloc = 0;
-int * _fcBankId = 0, * _fcBankIdx = 0;
-static const char ** bankDirs = 0;
-
-static FcBool
-FcCacheHaveBank (int bank)
-{
- int i;
-
- if (bank < FC_BANK_FIRST)
- return FcTrue;
-
- for (i = 0; i < banks_ptr; i++)
- if (_fcBankId[i] == bank)
- return FcTrue;
-
+ FcStrFree (cache_hashed);
return FcFalse;
}
-int
-FcCacheBankToIndexMTF (int bank)
-{
- int i, j;
-
- for (i = 0; i < banks_ptr; i++)
- if (_fcBankId[_fcBankIdx[i]] == bank)
- {
- int t = _fcBankIdx[i];
-
- for (j = i; j > 0; j--)
- _fcBankIdx[j] = _fcBankIdx[j-1];
- _fcBankIdx[0] = t;
- return t;
- }
-
- if (banks_ptr >= banks_alloc)
- {
- int * b, * bidx;
- const char ** bds;
-
- b = realloc (_fcBankId, (banks_alloc + 4) * sizeof(int));
- if (!b)
- return -1;
- _fcBankId = b;
-
- bidx = realloc (_fcBankIdx, (banks_alloc + 4) * sizeof(int));
- if (!bidx)
- return -1;
- _fcBankIdx = bidx;
-
- bds = realloc (bankDirs, (banks_alloc + 4) * sizeof (char *));
- if (!bds)
- return -1;
- bankDirs = bds;
-
- banks_alloc += 4;
- }
-
- i = banks_ptr++;
- _fcBankId[i] = bank;
- _fcBankIdx[i] = i;
- return i;
-}
-
-static void
-FcCacheAddBankDir (int bank, const char * dir)
-{
- int bi = FcCacheBankToIndexMTF (bank);
-
- if (bi < 0)
- return;
-
- bankDirs[bi] = (const char *)FcStrCopy ((FcChar8 *)dir);
-}
-
-const char *
-FcCacheFindBankDir (int bank)
-{
- int bi = FcCacheBankToIndex (bank);
- return bankDirs[bi];
-}
-
/*
* This code implements the MD5 message-digest algorithm.
* The algorithm is due to Ron Rivest. This code was