1// jcache.c 2 3#ifdef OPT_JAMFILE_CACHE_EXT 4 5#include <stdio.h> 6#include <stdlib.h> 7#include <string.h> 8 9#include "jam.h" 10#include "jcache.h" 11#include "filesys.h" 12#include "hash.h" 13#include "lists.h" 14#include "newstr.h" 15#include "pathsys.h" 16#include "parse.h" 17#include "rules.h" 18#include "search.h" 19#include "variable.h" 20 21/////////////// 22// string_list 23// 24 25typedef struct string_list { 26 char** strings; // null terminated array of strings 27 int count; // number of strings in the array, not counting the 28 // terminating null 29 int capacity; // current capacity of the string array 30 int block_size; // granularity (number of entries) to be used for 31 // resizing the array 32} string_list; 33 34// string list prototypes 35static string_list* new_string_list(int block_size); 36static void delete_string_list(string_list* list); 37static int resize_string_list(string_list *list, int size); 38static int push_string(string_list *list, char *string); 39static char* pop_string(string_list *list); 40 41 42// file_read_line 43/*! \brief Reads a line from the supplied file and writes it to the supplied 44 buffer. 45 46 If the line ends in a LF, it is chopped off. 47 48 \param file The file. 49 \param value The pointer to where the read value shall be written. 50 \return \c ~0, if a number could be read, 0 otherwise. 51*/ 52static 53int 54file_read_line(FILE *file, char *buffer, int bufferSize) 55{ 56 int len; 57 58 if (!fgets(buffer, bufferSize, file)) 59 return 0; 60 61 len = strlen(buffer); 62 if (len > 0 && buffer[len - 1] == '\n') 63 buffer[len - 1] = '\0'; 64 65 return 1; 66} 67 68// file_read_line_long 69/*! \brief Reads a line from the supplied file and interprets it as long value. 70 71 This is almost equivalent to \code fscanf(file, "%ld", value) \endcode, 72 save that fscanf() seems to eat all following LFs, while this function 73 only reads one. 74 75 \param file The file. 76 \param value The pointer to where the read value shall be written. 77 \return \c ~0, if a number could be read, 0 otherwise. 78*/ 79static 80int 81file_read_line_long(FILE *file, long *value) 82{ 83 char buffer[32]; 84 int result; 85 86 result = file_read_line(file, buffer, sizeof(buffer)); 87 if (!result) 88 return result; 89 90 if (sscanf(buffer, "%ld\n", value) != 1) 91 return 0; 92 93 return 1; 94} 95 96// new_string_list 97/*! \brief Creates a new string_list. 98 \param block_size Granularity (number of entries) to be used for 99 resizing the string array. 100 \return Pointer to the newly allocated string_list, or 0 when out of 101 memory. 102*/ 103static 104string_list* 105new_string_list(int block_size) 106{ 107 string_list *list = (string_list*)malloc(sizeof(string_list)); 108 if (list) { 109 list->strings = 0; 110 list->count = 0; 111 list->capacity = 0; 112 if (block_size <= 0) 113 block_size = 5; 114 list->block_size = block_size; 115 if (!resize_string_list(list, 0)) { 116 free(list); 117 list = 0; 118 } 119 } 120 return list; 121} 122 123// delete_string_list 124/*! \brief Deletes a string_list formerly allocated with new_string_list. 125 126 All strings the list contains are freed as well. 127 128 \param list The string_list to be deleted. 129*/ 130static 131void 132delete_string_list(string_list* list) 133{ 134 if (list) { 135 if (list->strings) { 136 int i = 0; 137 for (i = 0; i < list->count; i++) { 138 if (list->strings[i]) 139 free(list->strings[i]); 140 } 141 free(list->strings); 142 } 143 free(list); 144 } 145} 146 147// resize_string_list 148/*! \brief Resizes the string array of a string_list. 149 150 \note This functions is for internal use only. 151 152 \param list The string_list to be resized. 153 \param size Number of entries the list shall be able to contain. 154 \return \c !0, if everything went fine, 0, if an error occured (out of 155 memory). 156*/ 157static 158int 159resize_string_list(string_list *list, int size) 160{ 161 int result = 0; 162 if (list) { 163 // capacity must be at least size + 1 and a multiple of the block size 164 int newCapacity = (size + list->block_size) 165 / list->block_size * list->block_size; 166 if (newCapacity == list->capacity) 167 result = !0; 168 else { 169 char** newStrings = (char**)realloc(list->strings, 170 newCapacity * sizeof(char*)); 171 if (newStrings) { 172 result = !0; 173 list->strings = newStrings; 174 list->capacity = newCapacity; 175 } 176 } 177 } 178 return result; 179} 180 181// push_string 182/*! \brief Appends a string to a string_list. 183 184 The list's string array is resized, if necessary and null terminated. 185 186 \param list The string_list. 187 \param string The string to be appended. 188 \return \c !0, if everything went fine, 0, if an error occured (out of 189 memory). 190*/ 191static 192int 193push_string(string_list *list, char *string) 194{ 195 int result = 0; 196 if (list) { 197 result = resize_string_list(list, list->count + 1); 198 if (result) { 199 list->strings[list->count] = string; 200 list->count++; 201 list->strings[list->count] = 0; // null terminate 202 } 203 } 204 return result; 205} 206 207// pop_string 208/*! \brief Removes the last string from a string_list. 209 210 The list's string array is resized, if necessary and null terminated. 211 The caller takes over ownership of the removed string and is responsible 212 for freeing it. 213 214 \param list The string_list. 215 \return The removed string, if everything went fine, 0 otherwise. 216*/ 217static 218char* 219pop_string(string_list *list) 220{ 221 char* string = 0; 222 if (list && list->count > 0) { 223 list->count--; 224 string = list->strings[list->count]; 225 list->strings[list->count] = 0; 226 resize_string_list(list, list->count); 227 } 228 return string; 229} 230 231 232/////////////////// 233// jamfile caching 234// 235 236// the jamfile cache 237typedef struct jamfile_cache { 238 struct hash* entries; // hash table of jcache_entrys 239 string_list* filenames; // entry filenames 240 char* cache_file; // name of the cache file 241} jamfile_cache; 242 243// a cache entry for an include file 244typedef struct jcache_entry { 245 char* filename; // name of the file 246 time_t time; // time stamp of the file 247 string_list* strings; // contents of the file 248 int used; // whether this cache entry has been used 249} jcache_entry; 250 251// pointer to the jamfile cache 252static jamfile_cache* jamfileCache = 0; 253 254// jamfile cache prototypes 255static jamfile_cache* new_jamfile_cache(void); 256static void delete_jamfile_cache(jamfile_cache* cache); 257static int init_jcache_entry(jcache_entry* entry, char *filename, time_t time, 258 int used); 259static void cleanup_jcache_entry(jcache_entry* entry); 260static int add_jcache_entry(jamfile_cache* cache, jcache_entry* entry); 261static jcache_entry* find_jcache_entry(jamfile_cache* cache, char* filename); 262static string_list* read_file(const char *filename, string_list* list); 263static int read_jcache(jamfile_cache* cache, char* filename); 264static int write_jcache(jamfile_cache* cache); 265static char* jcache_name(void); 266static jamfile_cache* get_jcache(void); 267 268// new_jamfile_cache 269/*! \brief Creates a new jamfile_cache. 270 \return A pointer to the newly allocated jamfile_cache, or 0, if out of 271 memory. 272*/ 273static 274jamfile_cache* 275new_jamfile_cache(void) 276{ 277 jamfile_cache *cache = (jamfile_cache*)malloc(sizeof(jamfile_cache)); 278 if (cache) { 279 cache->entries = hashinit(sizeof(jcache_entry), "jcache"); 280 cache->filenames = new_string_list(100); 281 cache->cache_file = 0; 282 if (!cache->entries || !cache->filenames) { 283 delete_jamfile_cache(cache); 284 cache = 0; 285 } 286 } 287 return cache; 288} 289 290// delete_jamfile_cache 291/*! \brief Deletes a jamfile_cache formerly allocated with new_jamfile_cache. 292 \param cache The jamfile_cache to be deleted. 293*/ 294static 295void 296delete_jamfile_cache(jamfile_cache* cache) 297{ 298 if (cache) { 299 if (cache->entries) 300 hashdone(cache->entries); 301 delete_string_list(cache->filenames); 302 } 303} 304 305// init_jcache_entry 306/*! \brief Initializes a pre-allocated jcache_entry. 307 \param entry The jcache_entry to be initialized. 308 \param filename The name of the include file to be associated with the 309 entry. 310 \param time The time stamp of the include file to be associated with the 311 entry. 312 \param used Whether or not the entry shall be marked used. 313 \return \c !0, if everything went fine, 0, if an error occured (out of 314 memory). 315*/ 316static 317int 318init_jcache_entry(jcache_entry* entry, char *filename, time_t time, int used) 319{ 320 int result = 0; 321 if (entry) { 322 result = !0; 323 entry->filename = (char*)malloc(strlen(filename) + 1); 324 if (entry->filename) 325 strcpy(entry->filename, filename); 326 entry->time = time; 327 entry->strings = new_string_list(100); 328 entry->used = used; 329 // cleanup on error 330 if (!entry->filename || !entry->strings) { 331 cleanup_jcache_entry(entry); 332 result = 0; 333 } 334 } 335 return result; 336} 337 338// cleanup_jcache_entry 339/*! \brief De-initializes a jcache_entry. 340 341 All resources associated with the entry, save the memory for the entry 342 structure itself, are freed. 343 344 \param entry The jcache_entry to be de-initialized. 345*/ 346static 347void 348cleanup_jcache_entry(jcache_entry* entry) 349{ 350 if (entry) { 351 if (entry->filename) 352 free(entry->filename); 353 if (entry->strings) 354 delete_string_list(entry->strings); 355 } 356} 357 358// add_jcache_entry 359/*! \brief Adds a jcache_entry to a jamfile_cache. 360 \param cache The jamfile_cache. 361 \param entry The jcache_entry to be added. 362 \return \c !0, if everything went fine, 0, if an error occured (out of 363 memory). 364*/ 365static 366int 367add_jcache_entry(jamfile_cache* cache, jcache_entry* entry) 368{ 369 int result = 0; 370 if (cache && entry) { 371 result = push_string(cache->filenames, entry->filename); 372 if (result) { 373 result = hashenter(cache->entries, (HASHDATA**)&entry); 374 if (!result) 375 pop_string(cache->filenames); 376 } 377 } 378 return result; 379} 380 381// find_jcache_entry 382/*! \brief Looks up jcache_entry in a jamfile_cache. 383 \param cache The jamfile_cache. 384 \param filename The name of the include file for whose jcache_entry shall 385 be retrieved. 386 \return A pointer to the found jcache_entry, or 0, if the cache does not 387 contain an entry for the specified filename. 388*/ 389static 390jcache_entry* 391find_jcache_entry(jamfile_cache* cache, char* filename) 392{ 393 jcache_entry _entry; 394 jcache_entry* entry = &_entry; 395 entry->filename = filename; 396 if (!hashcheck(cache->entries, (HASHDATA**)&entry)) 397 entry = 0; 398 return entry; 399} 400 401// read_file 402/*! \brief Reads in a text file and returns its contents as a string_list. 403 404 The function strips leading white spaces from each line and omits empty 405 or comment lines. 406 407 If a string_list is supplied via \a list the file's content is appended 408 to this list, otherwise a new string_list is allocated. 409 410 \param filename The name of the file to be read in. 411 \param list Pointer to a pre-allocated string_list. May be 0. 412 \return A pointer to the string_list containing the contents of the file, 413 or 0, if an error occured. 414*/ 415static 416string_list* 417read_file(const char *filename, string_list* list) 418{ 419 int result = 0; 420 FILE *file = 0; 421 string_list *allocatedList = 0; 422 // open file 423 if ((file = fopen(filename, "r")) != 0 424 && (list || (list = allocatedList = new_string_list(100)) != 0)) { 425 // read the file 426 char buffer[513]; 427 result = !0; 428 while (result && fgets(buffer, sizeof(buffer) - 1, file)) { 429 char* line = buffer; 430 int len = 0; 431 char *string = 0; 432 // skip leading white spaces 433 while (*line == ' ' || *line == '\t' || *line == '\n') 434 line++; 435 // make empty and comment lines simple new-line lines 436 if (!*line || *line == '#') { 437 line[0] = '\n'; 438 line[1] = '\0'; 439 } 440 len = strlen(line); 441 // make sure, the line ends in a LF 442 if (line[len - 1] != '\n') { 443 line[len] = '\n'; 444 len++; 445 line[len] = '\0'; 446 } 447 if ((size_t)len + 1 == sizeof(buffer)) { 448 fprintf(stderr, "error: %s:%d: line too long!\n", filename, 449 list->count + 1); 450 exit(1); 451 } 452 // copy it 453 string = (char*)malloc(len + 1); 454 if (string) { 455 strcpy(string, line); 456 result = push_string(list, string); 457 } else 458 result = 0; 459 } 460 // close the file 461 fclose(file); 462 } else 463 perror(filename); 464 // cleanup on error 465 if (!result) { 466 delete_string_list(allocatedList); 467 list = 0; 468 } 469 return list; 470} 471 472// read_jcache 473/*! \brief Reads a jamfile_cache from a file. 474 475 Only cache entries for files, that don't have an entry in \a cache yet, are 476 added to it. 477 478 \param cache The jamfile_cache the cache stored in the file shall be added 479 to. 480 \param filename The name of the file containing the cache to be read. 481 \return \c !0, if everything went fine, 0, if an error occured. 482*/ 483static 484int 485read_jcache(jamfile_cache* cache, char* filename) 486{ 487 int result = 0; 488 if (cache && filename) { 489 // open file 490 FILE *file = 0; 491 cache->cache_file = filename; 492 if ((file = fopen(filename, "r")) != 0) { 493 // read the file 494 char buffer[512]; 495 long count = 0; 496 int i; 497 result = !0; 498 // read number of cache entries 499 result = file_read_line_long(file, &count); 500 // read the cache entries 501 for (i = 0; result && i < count; i++) { 502 char entryname[PATH_MAX]; 503 long lineCount = 0; 504 time_t time = 0; 505 jcache_entry entry = { 0, 0, 0 }; 506 // entry name, time and line count 507 if (file_read_line(file, entryname, sizeof(entryname)) 508 && strlen(entryname) > 0 509 && file_read_line_long(file, &time) 510 && file_read_line_long(file, &lineCount) 511 && (init_jcache_entry(&entry, entryname, time, 0)) != 0) { 512 // read the lines 513 int j; 514 for (j = 0; result && j < lineCount; j++) { 515 if (fgets(buffer, sizeof(buffer), file)) { 516 char *string = (char*)malloc(strlen(buffer) + 1); 517 if (string) { 518 strcpy(string, buffer); 519 result = push_string(entry.strings, string); 520 } else 521 result = 0; 522 } else { 523 fprintf(stderr, "warning: Invalid jamfile cache: " 524 "Unexpected end of file.\n"); 525 result = 0; 526 } 527 } 528 } else { 529 fprintf(stderr, "warning: Invalid jamfile cache: " 530 "Failed to read file info.\n"); 531 result = 0; 532 } 533 if (result) { 534 // add only, if there's no entry for that file yet 535 if (find_jcache_entry(cache, entry.filename)) 536 cleanup_jcache_entry(&entry); 537 else 538 result = add_jcache_entry(cache, &entry); 539 } 540 // cleanup on error 541 if (!result) 542 cleanup_jcache_entry(&entry); 543 } 544 // close the file 545 fclose(file); 546 } // else: Couldn't open cache file. Don't worry. 547 } 548 return result; 549} 550 551// write_jcache 552/*! \brief Writes a jamfile_cache into a file. 553 \param cache The jamfile_cache that shall be stored in the file. 554 \param filename The name of the file the cache shall be stored in. 555 \return \c !0, if everything went fine, 0, if an error occured. 556*/ 557static 558int 559write_jcache(jamfile_cache* cache) 560{ 561 int result = 0; 562 if (cache && cache->cache_file) { 563 // open file 564 FILE *file = 0; 565 if ((file = fopen(cache->cache_file, "w")) != 0) { 566 int count = cache->filenames->count; 567 int i; 568 // write number of cache entries 569 result = (fprintf(file, "%d\n", count) > 0); 570 // write the cache entries 571 for (i = 0; result && i < count; i++) { 572 char* entryname = cache->filenames->strings[i]; 573 jcache_entry* entry = find_jcache_entry(cache, entryname); 574 // entry name, time and line count 575 if (!entry) { 576 result = 0; 577 } else if (!entry->strings || !entry->used) { 578 // just skip the entry, if it is not loaded or not used 579 } else if (fprintf(file, "%s\n", entryname) > 0 580 && (fprintf(file, "%ld\n", entry->time) > 0) 581 && (fprintf(file, "%d\n", entry->strings->count) > 0)) { 582 int j; 583 // the lines 584 for (j = 0; result && j < entry->strings->count; j++) { 585 const char* string = entry->strings->strings[j]; 586 result = (fwrite(string, strlen(string), 1, file) > 0); 587 } 588 } else 589 result = 0; 590 } 591 // close the file 592 fclose(file); 593 } 594 } 595 return result; 596} 597 598// jcache_name 599/*! \brief Returns the name of the file containing the global jamfile_cache. 600 601 The returned filename is the path to the target stored in the jam variable 602 \c JCACHEFILE. The string does not need to be freed. 603 604 \return A pointer to the jamfile cache file, or 0, if the jam variable is 605 not set yet, or an error occured. 606*/ 607static 608char* 609jcache_name(void) 610{ 611 static char* name = 0; 612 if (!name) { 613 LIST *jcachevar = var_get("JCACHEFILE"); 614 615 if (jcachevar) { 616 TARGET *t = bindtarget( jcachevar->string ); 617 618 pushsettings( t->settings ); 619 t->boundname = search( t->name, &t->time ); 620 popsettings( t->settings ); 621 622 if (t->boundname) { 623 name = (char*)copystr(t->boundname); 624 } 625 } 626 } 627 return name; 628} 629 630// get_jcache 631/*! \brief Returns a pointer to the global jamfile_cache. 632 633 The cache is being lazy-allocated. 634 635 \return A pointer to the global jamfile_cache, or 0, if an error occured. 636*/ 637static 638jamfile_cache* 639get_jcache(void) 640{ 641 if (!jamfileCache) 642 jamfileCache = new_jamfile_cache(); 643 if (jamfileCache && !jamfileCache->cache_file) { 644 char* filename = jcache_name(); 645 if (filename) { 646 if (!read_jcache(jamfileCache, filename)) { 647 // An error occurred while reading the cache file. Remove all 648 // entries that we read in, assuming they might be corrupted. 649 // Since the hash doesn't support removing entries, we create 650 // a new one and copy over the entries we want to keep. 651 int count = jamfileCache->filenames->count; 652 int i; 653 654 jamfile_cache* newCache = new_jamfile_cache(); 655 if (!newCache) { 656 fprintf(stderr, "Out of memory!\n"); 657 exit(1); 658 } 659 660 for (i = 0; i < count; i++) { 661 char* entryname = jamfileCache->filenames->strings[i]; 662 jcache_entry* entry = find_jcache_entry(jamfileCache, 663 entryname); 664 if (entry->used) { 665 jcache_entry newEntry; 666 if (!init_jcache_entry(&newEntry, entryname, 667 entry->time, entry->used)) { 668 fprintf(stderr, "Out of memory!\n"); 669 exit(1); 670 } 671 672 delete_string_list(newEntry.strings); 673 newEntry.strings = entry->strings; 674 entry->strings = 0; 675 676 if (!add_jcache_entry(newCache, &newEntry)) { 677 fprintf(stderr, "Out of memory!\n"); 678 exit(1); 679 } 680 } 681 } 682 683 delete_jamfile_cache(jamfileCache); 684 jamfileCache = newCache; 685 jamfileCache->cache_file = filename; 686 } 687 } 688 } 689 return jamfileCache; 690} 691 692// jcache_init 693/*! \brief To be called before using the global jamfile_cache. 694 695 Does nothing currently. The global jamfile_cache is lazy-allocated by 696 get_jcache(). 697*/ 698void 699jcache_init(void) 700{ 701} 702 703// jcache_done 704/*! \brief To be called when done with the global jamfile_cache. 705 706 Writes the cache to the specified cache file. 707*/ 708void 709jcache_done(void) 710{ 711 jamfile_cache* cache = get_jcache(); 712 if (cache) { 713 write_jcache(cache); 714 delete_jamfile_cache(cache); 715 jamfileCache = 0; 716 } 717} 718 719// jcache 720/*! \brief Returns the contents of an include file as a null terminated string 721 array. 722 723 If the file is cached and the respective entry is not obsolete, the cached 724 string array is returned, otherwise the file is read in. 725 726 The caller must not free the returned string array or any of the contained 727 strings. 728 729 \param filename The name of the include file. 730 \return A pointer to a null terminated string array representing the 731 contents of the specified file, or 0, if an error occured. 732*/ 733char** 734jcache(char *_filename) 735{ 736 char** strings = 0; 737 jamfile_cache* cache = get_jcache(); 738 time_t time; 739 // normalize the filename 740 char _normalizedPath[PATH_MAX]; 741 char *filename = normalize_path(_filename, _normalizedPath, 742 sizeof(_normalizedPath)); 743 if (!filename) 744 filename = _filename; 745 // get file time 746 if (!cache) 747 return 0; 748 if (file_time(filename, &time) == 0) { 749 // lookup file in cache 750 jcache_entry* entry = find_jcache_entry(cache, filename); 751 if (entry) { 752 // in cache 753 entry->used = !0; 754 if (entry->time == time && entry->strings) { 755 // up to date 756 strings = entry->strings->strings; 757 } else { 758 // obsolete 759 delete_string_list(entry->strings); 760 entry->strings = read_file(filename, 0); 761 entry->time = time; 762 strings = entry->strings->strings; 763 } 764 } else { 765 // not in cache 766 jcache_entry newEntry; 767 entry = &newEntry; 768 init_jcache_entry(entry, filename, time, !0); 769 if (read_file(filename, entry->strings)) { 770 if (add_jcache_entry(cache, entry)) 771 strings = entry->strings->strings; 772 } 773 // cleanup on error 774 if (!strings) 775 cleanup_jcache_entry(entry); 776 } 777 } else 778 perror(filename); 779 return strings; 780} 781 782#endif // OPT_JAMFILE_CACHE_EXT 783