#ifdef OPT_JAMFILE_CACHE_EXT
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "jam.h"
#include "jcache.h"
#include "filesys.h"
#include "hash.h"
#include "lists.h"
#include "newstr.h"
#include "pathsys.h"
#include "parse.h"
#include "rules.h"
#include "search.h"
#include "variable.h"
typedef struct string_list {
char** strings;
int count;
int capacity;
int block_size;
} string_list;
static string_list* new_string_list(int block_size);
static void delete_string_list(string_list* list);
static int resize_string_list(string_list *list, int size);
static int push_string(string_list *list, char *string);
static char* pop_string(string_list *list);
buffer.
If the line end in a LF, it is chopped off.
\param file The file.
\param value The pointer to where the read value shall be written.
\return \c ~0, if a number could be read, 0 otherwise.
*/
static
int
file_read_line(FILE *file, char *buffer, int bufferSize)
{
int len;
if (!fgets(buffer, bufferSize, file))
return 0;
len = strlen(buffer);
if (len > 0 && buffer[len - 1] == '\n')
buffer[len - 1] = '\0';
return 1;
}
This is almost equivalent to \code fscanf(file, "%ld", value) \endcode,
save that fscanf() seems to eat all following LFs, while this function
only reads one.
\param file The file.
\param value The pointer to where the read value shall be written.
\return \c ~0, if a number could be read, 0 otherwise.
*/
static
int
file_read_line_long(FILE *file, long *value)
{
char buffer[32];
int result;
result = file_read_line(file, buffer, sizeof(buffer));
if (!result)
return result;
if (sscanf(buffer, "%ld\n", value) != 1)
return 0;
return 1;
}
\param block_size Granularity (number of entries) to be used for
resizing the string array.
\return Pointer to the newly allocated string_list, or 0 when out of
memory.
*/
static
string_list*
new_string_list(int block_size)
{
string_list *list = (string_list*)malloc(sizeof(string_list));
if (list) {
list->strings = 0;
list->count = 0;
list->capacity = 0;
if (block_size <= 0)
block_size = 5;
list->block_size = block_size;
if (!resize_string_list(list, 0)) {
free(list);
list = 0;
}
}
return list;
}
All strings the list contains are freed as well.
\param list The string_list to be deleted.
*/
static
void
delete_string_list(string_list* list)
{
if (list) {
if (list->strings) {
int i = 0;
for (i = 0; i < list->count; i++) {
if (list->strings[i])
free(list->strings[i]);
}
free(list->strings);
}
free(list);
}
}
\note This functions is for internal use only.
\param list The string_list to be resized.
\param size Number of entries the list shall be able to contain.
\return \c !0, if everything went fine, 0, if an error occured (out of
memory).
*/
static
int
resize_string_list(string_list *list, int size)
{
int result = 0;
if (list) {
int newCapacity = (size + list->block_size)
/ list->block_size * list->block_size;
if (newCapacity == list->capacity)
result = !0;
else {
char** newStrings = (char**)realloc(list->strings,
newCapacity * sizeof(char*));
if (newStrings) {
result = !0;
list->strings = newStrings;
list->capacity = newCapacity;
}
}
}
return result;
}
The list's string array is resized, if necessary and null terminated.
\param list The string_list.
\param string The string to be appended.
\return \c !0, if everything went fine, 0, if an error occured (out of
memory).
*/
static
int
push_string(string_list *list, char *string)
{
int result = 0;
if (list) {
result = resize_string_list(list, list->count + 1);
if (result) {
list->strings[list->count] = string;
list->count++;
list->strings[list->count] = 0;
}
}
return result;
}
The list's string array is resized, if necessary and null terminated.
The caller takes over ownership of the removed string and is responsible
for freeing it.
\param list The string_list.
\return The removed string, if everything went fine, 0 otherwise.
*/
static
char*
pop_string(string_list *list)
{
char* string = 0;
if (list && list->count > 0) {
list->count--;
string = list->strings[list->count];
list->strings[list->count] = 0;
resize_string_list(list, list->count);
}
return string;
}
typedef struct jamfile_cache {
struct hash* entries;
string_list* filenames;
char* cache_file;
} jamfile_cache;
typedef struct jcache_entry {
char* filename;
time_t time;
string_list* strings;
int used;
} jcache_entry;
static jamfile_cache* jamfileCache = 0;
static jamfile_cache* new_jamfile_cache(void);
static void delete_jamfile_cache(jamfile_cache* cache);
static int init_jcache_entry(jcache_entry* entry, char *filename, time_t time,
int used);
static void cleanup_jcache_entry(jcache_entry* entry);
static int add_jcache_entry(jamfile_cache* cache, jcache_entry* entry);
static jcache_entry* find_jcache_entry(jamfile_cache* cache, char* filename);
static string_list* read_file(const char *filename, string_list* list);
static int read_jcache(jamfile_cache* cache, char* filename);
static int write_jcache(jamfile_cache* cache);
static char* jcache_name(void);
static jamfile_cache* get_jcache(void);
\return A pointer to the newly allocated jamfile_cache, or 0, if out of
memory.
*/
static
jamfile_cache*
new_jamfile_cache(void)
{
jamfile_cache *cache = (jamfile_cache*)malloc(sizeof(jamfile_cache));
if (cache) {
cache->entries = hashinit(sizeof(jcache_entry), "jcache");
cache->filenames = new_string_list(100);
cache->cache_file = 0;
if (!cache->entries || !cache->filenames) {
delete_jamfile_cache(cache);
cache = 0;
}
}
return cache;
}
\param cache The jamfile_cache to be deleted.
*/
static
void
delete_jamfile_cache(jamfile_cache* cache)
{
if (cache) {
if (cache->entries)
hashdone(cache->entries);
delete_string_list(cache->filenames);
free(cache->cache_file);
}
}
\param entry The jcache_entry to be initialized.
\param filename The name of the include file to be associated with the
entry.
\param time The time stamp of the include file to be associated with the
entry.
\param used Whether or not the entry shall be marked used.
\return \c !0, if everything went fine, 0, if an error occured (out of
memory).
*/
static
int
init_jcache_entry(jcache_entry* entry, char *filename, time_t time, int used)
{
int result = 0;
if (entry) {
result = !0;
entry->filename = (char*)malloc(strlen(filename) + 1);
if (entry->filename)
strcpy(entry->filename, filename);
entry->time = time;
entry->strings = new_string_list(100);
entry->used = used;
if (!entry->filename || !entry->strings) {
cleanup_jcache_entry(entry);
result = 0;
}
}
return result;
}
All resources associated with the entry, save the memory for the entry
structure itself, are freed.
\param entry The jcache_entry to be de-initialized.
*/
static
void
cleanup_jcache_entry(jcache_entry* entry)
{
if (entry) {
if (entry->filename)
free(entry->filename);
if (entry->strings)
delete_string_list(entry->strings);
}
}
\param cache The jamfile_cache.
\param entry The jcache_entry to be added.
\return \c !0, if everything went fine, 0, if an error occured (out of
memory).
*/
static
int
add_jcache_entry(jamfile_cache* cache, jcache_entry* entry)
{
int result = 0;
if (cache && entry) {
result = push_string(cache->filenames, entry->filename);
if (result) {
result = hashenter(cache->entries, (HASHDATA**)&entry);
if (!result)
pop_string(cache->filenames);
}
}
return result;
}
\param cache The jamfile_cache.
\param filename The name of the include file for whose jcache_entry shall
be retrieved.
\return A pointer to the found jcache_entry, or 0, if the cache does not
contain an entry for the specified filename.
*/
static
jcache_entry*
find_jcache_entry(jamfile_cache* cache, char* filename)
{
jcache_entry _entry;
jcache_entry* entry = &_entry;
entry->filename = filename;
if (!hashcheck(cache->entries, (HASHDATA**)&entry))
entry = 0;
return entry;
}
The function strips leading white spaces from each line and omits empty
or comment lines.
If a string_list is supplied via \a list the file's content is appended
to this list, otherwise a new string_list is allocated.
\param filename The name of the file to be read in.
\param list Pointer to a pre-allocated string_list. May be 0.
\return A pointer to the string_list containing the contents of the file,
or 0, if an error occured.
*/
static
string_list*
read_file(const char *filename, string_list* list)
{
int result = 0;
FILE *file = 0;
string_list *allocatedList = 0;
if ((file = fopen(filename, "r")) != 0
&& (list || (list = allocatedList = new_string_list(100)) != 0)) {
char buffer[513];
result = !0;
while (result && fgets(buffer, sizeof(buffer) - 1, file)) {
char* line = buffer;
int len = 0;
char *string = 0;
while (*line == ' ' || *line == '\t' || *line == '\n')
line++;
if (!*line || *line == '#') {
line[0] = '\n';
line[1] = '\0';
}
len = strlen(line);
if (line[len - 1] != '\n') {
line[len] = '\n';
len++;
line[len] = '\0';
}
string = (char*)malloc(len + 1);
if (string) {
strcpy(string, line);
result = push_string(list, string);
} else
result = 0;
}
fclose(file);
} else
perror(filename);
if (!result) {
delete_string_list(allocatedList);
list = 0;
}
return list;
}
Only cache entries for files, that don't have an entry in \a cache yet, are
added to it.
\param cache The jamfile_cache the cache stored in the file shall be added
to.
\param filename The name of the file containing the cache to be read.
\return \c !0, if everything went fine, 0, if an error occured.
*/
static
int
read_jcache(jamfile_cache* cache, char* filename)
{
int result = 0;
if (cache && filename) {
FILE *file = 0;
cache->cache_file = filename;
if ((file = fopen(filename, "r")) != 0) {
char buffer[512];
long count = 0;
int i;
result = !0;
result = file_read_line_long(file, &count);
for (i = 0; result && i < count; i++) {
char entryname[PATH_MAX];
long lineCount = 0;
time_t time = 0;
jcache_entry entry = { 0, 0, 0 };
if (file_read_line(file, entryname, sizeof(entryname))
&& strlen(entryname) > 0
&& file_read_line_long(file, &time)
&& file_read_line_long(file, &lineCount)
&& (init_jcache_entry(&entry, entryname, time, 0)) != 0) {
int j;
for (j = 0; result && j < lineCount; j++) {
if (fgets(buffer, sizeof(buffer), file)) {
char *string = (char*)malloc(strlen(buffer) + 1);
if (string) {
strcpy(string, buffer);
result = push_string(entry.strings, string);
} else
result = 0;
} else {
fprintf(stderr, "warning: Invalid jamfile cache: "
"Unexpected end of file.\n");
result = 0;
}
}
} else {
fprintf(stderr, "warning: Invalid jamfile cache: "
"Failed to read file info.\n");
result = 0;
}
if (result) {
if (find_jcache_entry(cache, entry.filename))
cleanup_jcache_entry(&entry);
else
result = add_jcache_entry(cache, &entry);
}
if (!result)
cleanup_jcache_entry(&entry);
}
fclose(file);
}
}
return result;
}
\param cache The jamfile_cache that shall be stored in the file.
\param filename The name of the file the cache shall be stored in.
\return \c !0, if everything went fine, 0, if an error occured.
*/
static
int
write_jcache(jamfile_cache* cache)
{
int result = 0;
if (cache && cache->cache_file) {
FILE *file = 0;
if ((file = fopen(cache->cache_file, "w")) != 0) {
int count = cache->filenames->count;
int i;
result = (fprintf(file, "%d\n", count) > 0);
for (i = 0; result && i < count; i++) {
char* entryname = cache->filenames->strings[i];
jcache_entry* entry = find_jcache_entry(cache, entryname);
if (!entry) {
result = 0;
} else if (!entry->strings || !entry->used) {
} else if (fprintf(file, "%s\n", entryname) > 0
&& (fprintf(file, "%ld\n", entry->time) > 0)
&& (fprintf(file, "%d\n", entry->strings->count) > 0)) {
int j;
for (j = 0; result && j < entry->strings->count; j++) {
result = (fprintf(file,
entry->strings->strings[j]) > 0);
}
} else
result = 0;
}
fclose(file);
}
}
return result;
}
The returned filename is the path to the target stored in the jam variable
\c JCACHEFILE. The string does not need to be freed.
\return A pointer to the jamfile cache file, or 0, if the jam variable is
not set yet, or an error occured.
*/
static
char*
jcache_name(void)
{
static char* name = 0;
if (!name) {
LIST *jcachevar = var_get("JCACHEFILE");
if (jcachevar) {
TARGET *t = bindtarget( jcachevar->string );
pushsettings( t->settings );
t->boundname = search( t->name, &t->time );
popsettings( t->settings );
if (t->boundname) {
name = (char*)copystr(t->boundname);
}
}
}
return name;
}
The cache is being lazy-allocated.
\return A pointer to the global jamfile_cache, or 0, if an error occured.
*/
static
jamfile_cache*
get_jcache(void)
{
if (!jamfileCache)
jamfileCache = new_jamfile_cache();
if (jamfileCache && !jamfileCache->cache_file) {
char* filename = jcache_name();
if (filename)
read_jcache(jamfileCache, filename);
}
return jamfileCache;
}
Does nothing currently. The global jamfile_cache is lazy-allocated by
get_jcache().
*/
void
jcache_init(void)
{
}
Writes the cache to the specified cache file.
*/
void
jcache_done(void)
{
jamfile_cache* cache = get_jcache();
if (cache) {
write_jcache(cache);
delete_jamfile_cache(cache);
jamfileCache = 0;
}
}
array.
If the file is cached and the respective entry is not obsolete, the cached
string array is returned, otherwise the file is read in.
The caller must not free the returned string array or any of the contained
strings.
\param filename The name of the include file.
\return A pointer to a null terminated string array representing the
contents of the specified file, or 0, if an error occured.
*/
char**
jcache(char *_filename)
{
char** strings = 0;
jamfile_cache* cache = get_jcache();
time_t time;
char _normalizedPath[PATH_MAX];
char *filename = normalize_path(_filename, _normalizedPath,
sizeof(_normalizedPath));
if (!filename)
filename = _filename;
if (!cache)
return 0;
if (file_time(filename, &time) == 0) {
jcache_entry* entry = find_jcache_entry(cache, filename);
if (entry) {
entry->used = !0;
if (entry->time == time && entry->strings) {
strings = entry->strings->strings;
} else {
delete_string_list(entry->strings);
entry->strings = read_file(filename, 0);
entry->time = time;
strings = entry->strings->strings;
}
} else {
jcache_entry newEntry;
entry = &newEntry;
init_jcache_entry(entry, filename, time, !0);
if (read_file(filename, entry->strings)) {
if (add_jcache_entry(cache, entry))
strings = entry->strings->strings;
}
if (!strings)
cleanup_jcache_entry(entry);
}
} else
perror(filename);
return strings;
}
#endif