/* * $XFree86: xc/lib/fontconfig/src/fccache.c,v 1.7 2002/05/21 17:06:22 keithp Exp $ * * Copyright © 2000 Keith Packard, member of The XFree86 Project, Inc. * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation, and that the name of Keith Packard not be used in * advertising or publicity pertaining to distribution of the software without * specific, written prior permission. Keith Packard makes no * representations about the suitability of this software for any purpose. It * is provided "as is" without express or implied warranty. * * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include "fcint.h" #define FC_DBG_CACHE_REF 1024 static FcChar8 * FcCacheReadString (FILE *f, FcChar8 *dest, int len) { int c; FcBool escape; FcChar8 *d; int size; int i; while ((c = getc (f)) != EOF) if (c == '"') break; if (c == EOF) return FcFalse; if (len == 0) return FcFalse; size = len; i = 0; d = dest; escape = FcFalse; while ((c = getc (f)) != EOF) { if (!escape) { switch (c) { case '"': c = '\0'; break; case '\\': escape = FcTrue; continue; } } if (i == size) { FcChar8 *new = malloc (size * 2); if (!new) break; memcpy (new, d, size); size *= 2; if (d != dest) free (d); d = new; } d[i++] = c; if (c == '\0') return d; escape = FcFalse; } if (d != dest) free (d); return 0; } static FcBool FcCacheReadUlong (FILE *f, unsigned long *dest) { unsigned long t; int c; while ((c = getc (f)) != EOF) { if (!isspace (c)) break; } if (c == EOF) return FcFalse; t = 0; for (;;) { if (c == EOF || isspace (c)) break; if (!isdigit (c)) return FcFalse; t = t * 10 + (c - '0'); c = getc (f); } *dest = t; return FcTrue; } static FcBool FcCacheReadInt (FILE *f, int *dest) { unsigned long t; FcBool ret; ret = FcCacheReadUlong (f, &t); if (ret) *dest = (int) t; return ret; } static FcBool FcCacheReadTime (FILE *f, time_t *dest) { unsigned long t; FcBool ret; ret = FcCacheReadUlong (f, &t); if (ret) *dest = (time_t) t; return ret; } static FcBool FcCacheWriteChars (FILE *f, const FcChar8 *chars) { FcChar8 c; while ((c = *chars++)) { switch (c) { case '"': case '\\': if (putc ('\\', f) == EOF) return FcFalse; /* fall through */ default: if (putc (c, f) == EOF) return FcFalse; } } return FcTrue; } static FcBool FcCacheWriteString (FILE *f, const FcChar8 *string) { if (putc ('"', f) == EOF) return FcFalse; if (!FcCacheWriteChars (f, string)) return FcFalse; if (putc ('"', f) == EOF) return FcFalse; return FcTrue; } static FcBool FcCacheWritePath (FILE *f, const FcChar8 *dir, const FcChar8 *file) { if (putc ('"', f) == EOF) return FcFalse; if (dir) if (!FcCacheWriteChars (f, dir)) return FcFalse; if (dir && dir[strlen((const char *) dir) - 1] != '/') if (putc ('/', f) == EOF) return FcFalse; if (!FcCacheWriteChars (f, file)) return FcFalse; if (putc ('"', f) == EOF) return FcFalse; return FcTrue; } static FcBool FcCacheWriteUlong (FILE *f, unsigned long t) { int pow; unsigned long temp, digit; temp = t; pow = 1; while (temp >= 10) { temp /= 10; pow *= 10; } temp = t; while (pow) { digit = temp / pow; if (putc ((char) digit + '0', f) == EOF) return FcFalse; temp = temp - pow * digit; pow = pow / 10; } return FcTrue; } static FcBool FcCacheWriteInt (FILE *f, int i) { return FcCacheWriteUlong (f, (unsigned long) i); } static FcBool FcCacheWriteTime (FILE *f, time_t t) { return FcCacheWriteUlong (f, (unsigned long) t); } static FcBool FcCacheFontSetAdd (FcFontSet *set, FcStrSet *dirs, const FcChar8 *dir, int dir_len, const FcChar8 *file, const FcChar8 *name) { FcChar8 path_buf[8192], *path; int len; FcBool ret = FcFalse; FcPattern *font; path = path_buf; len = (dir_len + 1 + strlen ((const char *) file) + 1); if (len > sizeof (path_buf)) { path = malloc (len); if (!path) return FcFalse; } strncpy ((char *) path, (const char *) dir, dir_len); if (dir[dir_len - 1] != '/') path[dir_len++] = '/'; strcpy ((char *) path + dir_len, (const char *) file); if (!FcStrCmp (name, FC_FONT_FILE_DIR)) { if (FcDebug () & FC_DBG_CACHEV) printf (" dir cache dir \"%s\"\n", path); ret = FcStrSetAdd (dirs, path); } else if (!FcStrCmp (name, FC_FONT_FILE_INVALID)) { ret = FcTrue; } else { font = FcNameParse (name); if (font) { if (FcDebug () & FC_DBG_CACHEV) printf (" dir cache file \"%s\"\n", file); ret = (FcPatternAddString (font, FC_FILE, path) && FcFontSetAdd (set, font)); if (!ret) FcPatternDestroy (font); } } if (path != path_buf) free (path); return ret; } static unsigned int FcCacheHash (const FcChar8 *string) { unsigned int h = 0; FcChar8 c; while ((c = *string++)) h = (h << 1) ^ c; return 0; } /* * Verify the saved timestamp for a file */ FcBool FcGlobalCacheCheckTime (FcGlobalCacheInfo *info) { struct stat statb; if (stat ((char *) info->file, &statb) < 0) { if (FcDebug () & FC_DBG_CACHE) printf (" file missing\n"); return FcFalse; } if (statb.st_mtime != info->time) { if (FcDebug () & FC_DBG_CACHE) printf (" timestamp mismatch (was %d is %d)\n", (int) info->time, (int) statb.st_mtime); return FcFalse; } return FcTrue; } void FcGlobalCacheReferenced (FcGlobalCache *cache, FcGlobalCacheInfo *info) { if (!info->referenced) { info->referenced = FcTrue; cache->referenced++; if (FcDebug () & FC_DBG_CACHE_REF) printf ("Reference %d %s\n", cache->referenced, info->file); } } /* * Break a path into dir/base elements and compute the base hash * and the dir length. This is shared between the functions * which walk the file caches */ typedef struct _FcFilePathInfo { const FcChar8 *dir; int dir_len; const FcChar8 *base; unsigned int base_hash; } FcFilePathInfo; static FcFilePathInfo FcFilePathInfoGet (const FcChar8 *path) { FcFilePathInfo i; FcChar8 *slash; slash = (FcChar8 *) strrchr ((const char *) path, '/'); if (slash) { i.dir = path; i.dir_len = slash - path; if (!i.dir_len) i.dir_len = 1; i.base = slash + 1; } else { i.dir = (const FcChar8 *) "."; i.dir_len = 1; i.base = path; } i.base_hash = FcCacheHash (i.base); return i; } FcGlobalCacheDir * FcGlobalCacheDirGet (FcGlobalCache *cache, const FcChar8 *dir, int len, FcBool create_missing) { unsigned int hash = FcCacheHash (dir); FcGlobalCacheDir *d, **prev; for (prev = &cache->ents[hash % FC_GLOBAL_CACHE_DIR_HASH_SIZE]; (d = *prev); prev = &(*prev)->next) { if (d->info.hash == hash && d->len == len && !strncmp ((const char *) d->info.file, (const char *) dir, len)) break; } if (!(d = *prev)) { int i; if (!create_missing) return 0; d = malloc (sizeof (FcGlobalCacheDir) + len + 1); if (!d) return 0; d->next = *prev; *prev = d; d->info.hash = hash; d->info.file = (FcChar8 *) (d + 1); strncpy ((char *) d->info.file, (const char *) dir, len); d->info.file[len] = '\0'; d->info.time = 0; d->info.referenced = FcFalse; d->len = len; for (i = 0; i < FC_GLOBAL_CACHE_FILE_HASH_SIZE; i++) d->ents[i] = 0; d->subdirs = 0; } return d; } static FcGlobalCacheInfo * FcGlobalCacheDirAdd (FcGlobalCache *cache, const FcChar8 *dir, time_t time, FcBool replace) { FcGlobalCacheDir *d; FcFilePathInfo i; FcGlobalCacheSubdir *subdir; FcGlobalCacheDir *parent; /* * Add this directory to the cache */ d = FcGlobalCacheDirGet (cache, dir, strlen ((const char *) dir), FcTrue); if (!d) return 0; d->info.time = time; i = FcFilePathInfoGet (dir); /* * Add this directory to the subdirectory list of the parent */ parent = FcGlobalCacheDirGet (cache, i.dir, i.dir_len, FcTrue); if (!parent) return 0; subdir = malloc (sizeof (FcGlobalCacheSubdir) + strlen ((const char *) i.base) + 1); if (!subdir) return 0; subdir->file = (FcChar8 *) (subdir + 1); strcpy ((char *) subdir->file, (const char *) i.base); subdir->next = parent->subdirs; parent->subdirs = subdir; return &d->info; } static void FcGlobalCacheDirDestroy (FcGlobalCacheDir *d) { FcGlobalCacheFile *f, *next; int h; FcGlobalCacheSubdir *s, *nexts; for (h = 0; h < FC_GLOBAL_CACHE_FILE_HASH_SIZE; h++) for (f = d->ents[h]; f; f = next) { next = f->next; free (f); } for (s = d->subdirs; s; s = nexts) { nexts = s->next; free (s); } free (d); } FcBool FcGlobalCacheScanDir (FcFontSet *set, FcStrSet *dirs, FcGlobalCache *cache, const FcChar8 *dir) { FcGlobalCacheDir *d = FcGlobalCacheDirGet (cache, dir, strlen ((const char *) dir), FcFalse); FcGlobalCacheFile *f; int h; int dir_len; FcGlobalCacheSubdir *subdir; if (FcDebug() & FC_DBG_CACHE) printf ("FcGlobalCacheScanDir %s\n", dir); if (!d) { if (FcDebug () & FC_DBG_CACHE) printf ("\tNo dir cache entry\n"); return FcFalse; } if (!FcGlobalCacheCheckTime (&d->info)) { if (FcDebug () & FC_DBG_CACHE) printf ("\tdir cache entry time mismatch\n"); return FcFalse; } dir_len = strlen ((const char *) dir); for (h = 0; h < FC_GLOBAL_CACHE_FILE_HASH_SIZE; h++) for (f = d->ents[h]; f; f = f->next) { if (FcDebug() & FC_DBG_CACHEV) printf ("FcGlobalCacheScanDir add file %s\n", f->info.file); if (!FcCacheFontSetAdd (set, dirs, dir, dir_len, f->info.file, f->name)) { cache->broken = FcTrue; return FcFalse; } FcGlobalCacheReferenced (cache, &f->info); } for (subdir = d->subdirs; subdir; subdir = subdir->next) { if (!FcCacheFontSetAdd (set, dirs, dir, dir_len, subdir->file, FC_FONT_FILE_DIR)) { cache->broken = FcTrue; return FcFalse; } } FcGlobalCacheReferenced (cache, &d->info); return FcTrue; } /* * Locate the cache entry for a particular file */ FcGlobalCacheFile * FcGlobalCacheFileGet (FcGlobalCache *cache, const FcChar8 *file, int id, int *count) { FcFilePathInfo i = FcFilePathInfoGet (file); FcGlobalCacheDir *d = FcGlobalCacheDirGet (cache, i.dir, i.dir_len, FcFalse); FcGlobalCacheFile *f, *match = 0; int max = -1; if (!d) return 0; for (f = d->ents[i.base_hash % FC_GLOBAL_CACHE_FILE_HASH_SIZE]; f; f = f->next) { if (f->info.hash == i.base_hash && !strcmp ((const char *) f->info.file, (const char *) i.base)) { if (f->id == id) match = f; if (f->id > max) max = f->id; } } if (count) *count = max; return match; } /* * Add a file entry to the cache */ static FcGlobalCacheInfo * FcGlobalCacheFileAdd (FcGlobalCache *cache, const FcChar8 *path, int id, time_t time, const FcChar8 *name, FcBool replace) { FcFilePathInfo i = FcFilePathInfoGet (path); FcGlobalCacheDir *d = FcGlobalCacheDirGet (cache, i.dir, i.dir_len, FcTrue); FcGlobalCacheFile *f, **prev; if (!d) return 0; for (prev = &d->ents[i.base_hash % FC_GLOBAL_CACHE_FILE_HASH_SIZE]; (f = *prev); prev = &(*prev)->next) { if (f->info.hash == i.base_hash && f->id == id && !strcmp ((const char *) f->info.file, (const char *) i.base)) { break; } } if (*prev) { if (!replace) return 0; f = *prev; if (f->info.referenced) cache->referenced--; *prev = f->next; free (f); } f = malloc (sizeof (FcGlobalCacheFile) + strlen ((char *) i.base) + 1 + strlen ((char *) name) + 1); if (!f) return 0; f->next = *prev; *prev = f; f->info.hash = i.base_hash; f->info.file = (FcChar8 *) (f + 1); f->info.time = time; f->info.referenced = FcFalse; f->id = id; f->name = f->info.file + strlen ((char *) i.base) + 1; strcpy ((char *) f->info.file, (const char *) i.base); strcpy ((char *) f->name, (const char *) name); return &f->info; } FcGlobalCache * FcGlobalCacheCreate (void) { FcGlobalCache *cache; int h; cache = malloc (sizeof (FcGlobalCache)); if (!cache) return 0; for (h = 0; h < FC_GLOBAL_CACHE_DIR_HASH_SIZE; h++) cache->ents[h] = 0; cache->entries = 0; cache->referenced = 0; cache->updated = FcFalse; cache->broken = FcFalse; return cache; } void FcGlobalCacheDestroy (FcGlobalCache *cache) { FcGlobalCacheDir *d, *next; int h; for (h = 0; h < FC_GLOBAL_CACHE_DIR_HASH_SIZE; h++) { for (d = cache->ents[h]; d; d = next) { next = d->next; FcGlobalCacheDirDestroy (d); } } free (cache); } /* * Cache file syntax is quite simple: * * "file_name" id time "font_name" \n */ void FcGlobalCacheLoad (FcGlobalCache *cache, const FcChar8 *cache_file) { FILE *f; FcChar8 file_buf[8192], *file; int id; time_t time; FcChar8 name_buf[8192], *name; FcGlobalCacheInfo *info; f = fopen ((char *) cache_file, "r"); if (!f) return; cache->updated = FcFalse; file = 0; name = 0; while ((file = FcCacheReadString (f, file_buf, sizeof (file_buf))) && FcCacheReadInt (f, &id) && FcCacheReadTime (f, &time) && (name = FcCacheReadString (f, name_buf, sizeof (name_buf)))) { if (FcDebug () & FC_DBG_CACHEV) printf ("FcGlobalCacheLoad \"%s\" \"%20.20s\"\n", file, name); if (!FcStrCmp (name, FC_FONT_FILE_DIR)) info = FcGlobalCacheDirAdd (cache, file, time, FcFalse); else info = FcGlobalCacheFileAdd (cache, file, id, time, name, FcFalse); if (!info) cache->broken = FcTrue; else cache->entries++; if (FcDebug () & FC_DBG_CACHE_REF) printf ("FcGlobalCacheLoad entry %d %s\n", cache->entries, file); if (file != file_buf) free (file); if (name != name_buf) free (name); file = 0; name = 0; } if (file && file != file_buf) free (file); if (name && name != name_buf) free (name); fclose (f); } FcBool FcGlobalCacheUpdate (FcGlobalCache *cache, const FcChar8 *file, int id, const FcChar8 *name) { const FcChar8 *match; struct stat statb; FcGlobalCacheInfo *info; match = file; if (stat ((char *) file, &statb) < 0) return FcFalse; if (S_ISDIR (statb.st_mode)) info = FcGlobalCacheDirAdd (cache, file, statb.st_mtime, FcTrue); else info = FcGlobalCacheFileAdd (cache, file, id, statb.st_mtime, name, FcTrue); if (info) { FcGlobalCacheReferenced (cache, info); cache->updated = FcTrue; } else cache->broken = FcTrue; return info != 0; } FcBool FcGlobalCacheSave (FcGlobalCache *cache, const FcChar8 *cache_file) { FILE *f; int dir_hash, file_hash; FcGlobalCacheDir *dir; FcGlobalCacheFile *file; FcAtomic *atomic; if (!cache->updated && cache->referenced == cache->entries) return FcTrue; if (cache->broken) return FcFalse; /* Set-UID programs can't safely update the cache */ if (getuid () != geteuid ()) return FcFalse; atomic = FcAtomicCreate (cache_file); if (!atomic) goto bail0; if (!FcAtomicLock (atomic)) goto bail1; f = fopen ((char *) FcAtomicNewFile(atomic), "w"); if (!f) goto bail2; for (dir_hash = 0; dir_hash < FC_GLOBAL_CACHE_DIR_HASH_SIZE; dir_hash++) { for (dir = cache->ents[dir_hash]; dir; dir = dir->next) { if (!dir->info.referenced) continue; if (!FcCacheWriteString (f, dir->info.file)) goto bail4; if (putc (' ', f) == EOF) goto bail4; if (!FcCacheWriteInt (f, 0)) goto bail4; if (putc (' ', f) == EOF) goto bail4; if (!FcCacheWriteTime (f, dir->info.time)) goto bail4; if (putc (' ', f) == EOF) goto bail4; if (!FcCacheWriteString (f, (FcChar8 *) FC_FONT_FILE_DIR)) goto bail4; if (putc ('\n', f) == EOF) goto bail4; for (file_hash = 0; file_hash < FC_GLOBAL_CACHE_FILE_HASH_SIZE; file_hash++) { for (file = dir->ents[file_hash]; file; file = file->next) { if (!file->info.referenced) continue; if (!FcCacheWritePath (f, dir->info.file, file->info.file)) goto bail4; if (putc (' ', f) == EOF) goto bail4; if (!FcCacheWriteInt (f, file->id < 0 ? 0 : file->id)) goto bail4; if (putc (' ', f) == EOF) goto bail4; if (!FcCacheWriteTime (f, file->info.time)) goto bail4; if (putc (' ', f) == EOF) goto bail4; if (!FcCacheWriteString (f, file->name)) goto bail4; if (putc ('\n', f) == EOF) goto bail4; } } } } if (fclose (f) == EOF) goto bail3; if (!FcAtomicReplaceOrig (atomic)) goto bail3; FcAtomicUnlock (atomic); FcAtomicDestroy (atomic); cache->updated = FcFalse; return FcTrue; bail4: fclose (f); bail3: FcAtomicDeleteNew (atomic); bail2: FcAtomicUnlock (atomic); bail1: FcAtomicDestroy (atomic); bail0: return FcFalse; } FcBool FcDirCacheValid (const FcChar8 *dir) { FcChar8 *cache_file = FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE); struct stat file_stat, dir_stat; if (stat ((char *) dir, &dir_stat) < 0) { FcStrFree (cache_file); return FcFalse; } if (stat ((char *) cache_file, &file_stat) < 0) { FcStrFree (cache_file); return FcFalse; } FcStrFree (cache_file); /* * If the directory has been modified more recently than * the cache file, the cache is not valid */ if (dir_stat.st_mtime - file_stat.st_mtime > 0) return FcFalse; return FcTrue; } FcBool FcDirCacheReadDir (FcFontSet *set, FcStrSet *dirs, const FcChar8 *dir) { FcChar8 *cache_file = FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE); FILE *f; FcChar8 *base; int id; int dir_len; FcChar8 file_buf[8192], *file; FcChar8 name_buf[8192], *name; FcBool ret = FcFalse; if (!cache_file) goto bail0; if (FcDebug () & FC_DBG_CACHE) printf ("FcDirCacheReadDir cache_file \"%s\"\n", cache_file); f = fopen ((char *) cache_file, "r"); if (!f) { if (FcDebug () & FC_DBG_CACHE) printf (" no cache file\n"); goto bail1; } if (!FcDirCacheValid (dir)) { if (FcDebug () & FC_DBG_CACHE) printf (" cache file older than directory\n"); goto bail2; } base = (FcChar8 *) strrchr ((char *) cache_file, '/'); if (!base) goto bail2; base++; dir_len = base - cache_file; file = 0; name = 0; while ((file = FcCacheReadString (f, file_buf, sizeof (file_buf))) && FcCacheReadInt (f, &id) && (name = FcCacheReadString (f, name_buf, sizeof (name_buf)))) { if (!FcCacheFontSetAdd (set, dirs, cache_file, dir_len, file, name)) goto bail3; if (file != file_buf) free (file); if (name != name_buf) free (name); file = name = 0; } if (FcDebug () & FC_DBG_CACHE) printf (" cache loaded\n"); ret = FcTrue; bail3: if (file && file != file_buf) free (file); if (name && name != name_buf) free (name); bail2: fclose (f); bail1: free (cache_file); bail0: return ret; } /* * return the path from the directory containing 'cache' to 'file' */ static const FcChar8 * FcFileBaseName (const FcChar8 *cache, const FcChar8 *file) { const FcChar8 *cache_slash; cache_slash = (const FcChar8 *) strrchr ((const char *) cache, '/'); if (cache_slash && !strncmp ((const char *) cache, (const char *) file, (cache_slash + 1) - cache)) return file + ((cache_slash + 1) - cache); return file; } FcBool FcDirCacheWriteDir (FcFontSet *set, FcStrSet *dirs, const FcChar8 *dir) { FcChar8 *cache_file = FcStrPlus (dir, (FcChar8 *) "/" FC_DIR_CACHE_FILE); FcPattern *font; FILE *f; FcChar8 *name; const FcChar8 *file, *base; int n; int id; FcBool ret; FcStrList *list; if (!cache_file) goto bail0; if (FcDebug () & FC_DBG_CACHE) printf ("FcDirCacheWriteDir cache_file \"%s\"\n", cache_file); f = fopen ((char *) cache_file, "w"); if (!f) { if (FcDebug () & FC_DBG_CACHE) printf (" can't create \"%s\"\n", cache_file); goto bail1; } list = FcStrListCreate (dirs); if (!list) goto bail2; while ((dir = FcStrListNext (list))) { base = FcFileBaseName (cache_file, dir); if (!FcCacheWriteString (f, base)) goto bail3; if (putc (' ', f) == EOF) goto bail3; if (!FcCacheWriteInt (f, 0)) goto bail3; if (putc (' ', f) == EOF) goto bail3; if (!FcCacheWriteString (f, FC_FONT_FILE_DIR)) goto bail3; if (putc ('\n', f) == EOF) goto bail3; } for (n = 0; n < set->nfont; n++) { font = set->fonts[n]; if (FcPatternGetString (font, FC_FILE, 0, (FcChar8 **) &file) != FcResultMatch) goto bail3; base = FcFileBaseName (cache_file, file); if (FcPatternGetInteger (font, FC_INDEX, 0, &id) != FcResultMatch) goto bail3; if (FcDebug () & FC_DBG_CACHEV) printf (" write file \"%s\"\n", base); if (!FcCacheWriteString (f, base)) goto bail3; if (putc (' ', f) == EOF) goto bail3; if (!FcCacheWriteInt (f, id)) goto bail3; if (putc (' ', f) == EOF) goto bail3; name = FcNameUnparse (font); if (!name) goto bail3; ret = FcCacheWriteString (f, name); free (name); if (!ret) goto bail3; if (putc ('\n', f) == EOF) goto bail3; } FcStrListDone (list); if (fclose (f) == EOF) goto bail1; free (cache_file); if (FcDebug () & FC_DBG_CACHE) printf (" cache written\n"); return FcTrue; bail3: FcStrListDone (list); bail2: fclose (f); bail1: unlink ((char *) cache_file); free (cache_file); bail0: return FcFalse; }