From 83e4a519853a3b54221ee1098297597dd6c1b766 Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Wed, 25 Sep 2024 16:12:25 +0900 Subject: [PATCH] Impl. html cache (mostly done) TODO: Invalidate cache if it is too old. --- src/arg_parse.c | 1 + src/helpers.c | 45 ++++++ src/helpers.h | 5 + src/html_cache.c | 368 ++++++++++++++++++++++++++++++++++++++++++++++- src/html_cache.h | 7 +- src/http.c | 46 +++++- src/http.h | 4 +- src/main.c | 4 +- src/test.c | 191 +++++++++++++++++++++++- 9 files changed, 658 insertions(+), 13 deletions(-) diff --git a/src/arg_parse.c b/src/arg_parse.c index 3a0c090..7cb7611 100644 --- a/src/arg_parse.c +++ b/src/arg_parse.c @@ -108,6 +108,7 @@ Args parse_args(int32_t argc, char **argv) { } else { printf("Directory \"%s\" exists.\n", args.cache_dir); } + closedir(d); } else { fprintf(stderr, "ERROR: Invalid args!\n"); print_usage(); diff --git a/src/helpers.c b/src/helpers.c index b391e22..c8b8cee 100644 --- a/src/helpers.c +++ b/src/helpers.c @@ -21,6 +21,13 @@ #include #include +// libc includes. +#include +#include +#include +#include +#include + int c_simple_http_internal_get_string_part_full_size(void *data, void *ud) { C_SIMPLE_HTTP_String_Part *part = data; size_t *count = ud; @@ -216,4 +223,42 @@ char *c_simple_http_helper_unescape_uri(const char *uri) { return c_simple_http_combine_string_parts(parts); } +int c_simple_http_helper_mkdir_tree(const char *path) { + // Check if dir already exists. + DIR *dir_ptr = opendir(path); + if (dir_ptr) { + // Directory already exists. + closedir(dir_ptr); + return 1; + } else if (errno == ENOENT) { + // Directory doesn't exist, create dir tree. + closedir(dir_ptr); + + size_t buf_size = strlen(path) + 1; + char *buf = malloc(buf_size); + memcpy(buf, path, buf_size - 1); + buf[buf_size - 1] = 0; + + char *dirname_buf = dirname(buf); + // Recursive call to ensure parent directories are created. + int ret = c_simple_http_helper_mkdir_tree(dirname_buf); + free(buf); + if (ret == 1 || ret == 0) { + // Parent directory should be created by now. + ret = mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); + if (ret != 0) { + return 4; + } + } else { + return 3; + } + + return 0; + } else { + // Other directory error. + closedir(dir_ptr); + return 2; + } +} + // vim: et ts=2 sts=2 sw=2 diff --git a/src/helpers.h b/src/helpers.h index bd649d9..5e1bd9f 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -58,6 +58,11 @@ char c_simple_http_helper_hex_to_value(const char upper, const char lower); /// non-NULL, it must be free'd. char *c_simple_http_helper_unescape_uri(const char *uri); +/// Returns zero if successful. "dirpath" will point to a directory on success. +/// Returns 1 if the directory already exists. +/// Other return values are errors. +int c_simple_http_helper_mkdir_tree(const char *dirpath); + #endif // vim: et ts=2 sts=2 sw=2 diff --git a/src/html_cache.c b/src/html_cache.c index 0f114b5..212ccba 100644 --- a/src/html_cache.c +++ b/src/html_cache.c @@ -16,9 +16,15 @@ // Standard library includes. #include +#include #include #include +// POSIX includes. +#include +#include +#include + // Third-party includes. #include #include @@ -26,6 +32,21 @@ // Local includes. #include "http.h" #include "helpers.h" +#include "http_template.h" + +int c_simple_http_internal_write_filenames_to_cache_file(void *data, void *ud) { + char *filename = data; + FILE *cache_fd = ud; + + const size_t filename_size = strlen(filename); + if (fwrite(filename, 1, filename_size, cache_fd) != filename_size) { + return 1; + } else if (fwrite("\n", 1, 1, cache_fd) != 1) { + return 1; + } + + return 0; +} char *c_simple_http_path_to_cache_filename(const char *path) { __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) @@ -185,8 +206,353 @@ int c_simple_http_cache_path( const char *path, const char *config_filename, const char *cache_dir, + const C_SIMPLE_HTTP_HTTPTemplates *templates, char **buf_out) { - // TODO + if (!path) { + fprintf(stderr, "ERROR cache_path function: path is NULL!\n"); + return -9; + } else if (!config_filename) { + fprintf(stderr, "ERROR cache_path function: config_filename is NULL!\n"); + return -10; + } else if (!cache_dir) { + fprintf(stderr, "ERROR cache_path function: cache_dir is NULL!\n"); + return -11; + } else if (!templates) { + fprintf(stderr, "ERROR cache_path function: templates is NULL!\n"); + return -12; + } else if (!buf_out) { + fprintf(stderr, "ERROR cache_path function: buf_out is NULL!\n"); + return -13; + } + + int ret = c_simple_http_helper_mkdir_tree(cache_dir); + if (ret != 0 && ret != 1) { + fprintf( + stderr, "ERROR failed to ensure cache_dir \"%s\" exists!\n", cache_dir); + return -15; + } + + // Get the cache filename from the path. + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) + char *cache_filename = c_simple_http_path_to_cache_filename(path); + if (!cache_filename) { + fprintf(stderr, "ERROR Failed to convert path to cache_filename!"); + return -1; + } + + // Combine the cache_dir with cache filename. + __attribute__((cleanup(simple_archiver_list_free))) + SDArchiverLinkedList *parts = simple_archiver_list_init(); + + c_simple_http_add_string_part(parts, cache_dir, 0); + c_simple_http_add_string_part(parts, "/", 0); + c_simple_http_add_string_part(parts, cache_filename, 0); + + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) + char *cache_filename_full = c_simple_http_combine_string_parts(parts); + + simple_archiver_list_free(&parts); + parts = simple_archiver_list_init(); + + if (!cache_filename_full) { + fprintf(stderr, "ERROR Failed to create full-path to cache filename!\n"); + return -2; + } + + // Get "stat" info on the cache filename. + uint_fast8_t force_cache_update = 0; + struct stat cache_file_stat; + ret = stat(cache_filename_full, &cache_file_stat); + if (ret == -1) { + if (errno == ENOENT) { + fprintf(stderr, "NOTICE cache file doesn't exist, will create...\n"); + } else { + fprintf( + stderr, + "ERROR getting stat info on file \"%s\" (errno %d)! " + "Assuming out of date!\n", + cache_filename_full, + errno); + } + force_cache_update = 1; + } + + // Get "stat" info on config file. + struct stat config_file_stat; + ret = stat(config_filename, &config_file_stat); + if (ret == -1) { + if (errno == ENOENT) { + fprintf( + stderr, "ERROR config file \"%s\" doesn't exist!\n", config_filename); + } else { + fprintf( + stderr, + "ERROR getting stat info on config file \"%s\" (errno %d)!\n", + config_filename, + errno); + } + return -3; + } + + if (!force_cache_update) { + do { + // Check filenames in cache file. + __attribute__((cleanup(simple_archiver_helper_cleanup_FILE))) + FILE *cache_fd = fopen(cache_filename_full, "r"); + const size_t buf_size = 1024; + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) + char *buf = malloc(buf_size); + + // Check header. + if (fread(buf, 1, 20, cache_fd) != 20) { + fprintf(stderr, "ERROR Failed to read header from cache file!\n"); + return -14; + } else if (strncmp(buf, "--- CACHE ENTRY ---\n", 20) != 0) { + fprintf( + stderr, + "WARNING Cache is invalid (bad header), assuming out of date!\n"); + force_cache_update = 1; + break; + } + + // Check filenames. + size_t buf_idx = 0; + while(1) { + ret = fgetc(cache_fd); + if (ret == EOF) { + fprintf( + stderr, "WARNING Cache is invalid (EOF), assuming out of date!\n"); + force_cache_update = 1; + break; + } else if (ret == '\n') { + // Got filename in "buf" of size "buf_idx". + if (strncmp(buf, "--- BEGIN HTML ---", 18) == 0) { + // Got end header instead of filename. + break; + } else if (buf_idx < buf_size) { + buf[buf_idx++] = 0; + } else { + fprintf( + stderr, + "WARNING Cache is invalid (too large filename), assuming out of " + "date!\n"); + force_cache_update = 1; + break; + } + + struct stat file_stat; + ret = stat(buf, &file_stat); + if (ret == -1) { + if (errno == ENOENT) { + fprintf( + stderr, + "WARNING Invalid filename cache entry \"%s\" (doesn't exist)! " + "Assuming out of date!\n", + buf); + force_cache_update = 1; + break; + } else { + fprintf( + stderr, + "WARNING Invalid filename cache entry \"%s\" (stat errno %d)! " + "Assuming out of date!\n", + buf, + errno); + force_cache_update = 1; + break; + } + } + + if (cache_file_stat.st_mtim.tv_sec < file_stat.st_mtim.tv_sec + || (cache_file_stat.st_mtim.tv_sec == file_stat.st_mtim.tv_sec + && cache_file_stat.st_mtim.tv_nsec + < file_stat.st_mtim.tv_nsec)) { + // File is newer than cache. + force_cache_update = 1; + break; + } + + buf_idx = 0; + } else if (buf_idx < buf_size) { + buf[buf_idx++] = (char)ret; + } else { + fprintf( + stderr, + "WARNING Cache is invalid (too large filename), assuming out of " + "date!\n"); + force_cache_update = 1; + break; + } + } + } while(0); + } + + // Compare modification times. +CACHE_FILE_WRITE_CHECK: + if (force_cache_update + || cache_file_stat.st_mtim.tv_sec < config_file_stat.st_mtim.tv_sec + || (cache_file_stat.st_mtim.tv_sec == config_file_stat.st_mtim.tv_sec + && cache_file_stat.st_mtim.tv_nsec < config_file_stat.st_mtim.tv_nsec)) + { + // Cache file is out of date. + __attribute__((cleanup(simple_archiver_helper_cleanup_FILE))) + FILE *cache_fd = fopen(cache_filename_full, "w"); + if (fwrite("--- CACHE ENTRY ---\n", 1, 20, cache_fd) != 20) { + fprintf( + stderr, + "ERROR Failed to write to cache file \"%s\"!\n", + cache_filename_full); + return -5; + } + + __attribute__((cleanup(simple_archiver_list_free))) + SDArchiverLinkedList *used_filenames = NULL; + + size_t generated_html_size = 0; + + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) + char *generated_html = c_simple_http_path_to_generated( + path, templates, &generated_html_size, &used_filenames); + + if (!generated_html) { + fprintf(stderr, "ERROR Failed to generate html for path \"%s\"!\n", path); + simple_archiver_helper_cleanup_FILE(&cache_fd); + remove(cache_filename_full); + return -4; + } + + if (simple_archiver_list_get( + used_filenames, + c_simple_http_internal_write_filenames_to_cache_file, + cache_fd)) { + fprintf(stderr, "ERROR Failed to write filenames to cache file!\n"); + return -6; + } else if (fwrite("--- BEGIN HTML ---\n", 1, 19, cache_fd) != 19) { + fprintf(stderr, "ERROR Failed to write end of cache file header!\n"); + return -7; + } else if ( + fwrite( + generated_html, + 1, + generated_html_size, + cache_fd) + != generated_html_size) { + fprintf(stderr, "ERROR Failed to write html to cache file!\n"); + return -8; + } + + *buf_out = generated_html; + generated_html = NULL; + return 1; + } + + // Cache file is newer. + __attribute__((cleanup(simple_archiver_helper_cleanup_FILE))) + FILE *cache_fd = fopen(cache_filename_full, "rb"); + + const size_t buf_size = 128; + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) + char *buf = malloc(buf_size); + + // Get first header. + if (fread(buf, 1, 20, cache_fd) != 20) { + fprintf( + stderr, + "WARNING Invalid cache file (read header), assuming out of date!\n"); + force_cache_update = 1; + goto CACHE_FILE_WRITE_CHECK; + } else if (strncmp("--- CACHE ENTRY ---\n", buf, 20) != 0) { + fprintf( + stderr, + "WARNING Invalid cache file (check header), assuming out of date!\n"); + force_cache_update = 1; + goto CACHE_FILE_WRITE_CHECK; + } + + // Get filenames end header. + uint_fast8_t reached_end_header = 0; + size_t buf_idx = 0; + while (1) { + ret = fgetc(cache_fd); + if (ret == EOF) { + fprintf( + stderr, "WARNING Invalid cache file (EOF), assuming out of date!\n"); + force_cache_update = 1; + goto CACHE_FILE_WRITE_CHECK; + } else if (ret == '\n') { + if (strncmp("--- BEGIN HTML ---", buf, 18) == 0) { + reached_end_header = 1; + break; + } + buf_idx = 0; + continue; + } + + if (buf_idx < buf_size) { + buf[buf_idx++] = (char)ret; + } + } + + if (!reached_end_header) { + fprintf( + stderr, + "WARNING Invalid cache file (no end header), assuming out of date!\n"); + force_cache_update = 1; + goto CACHE_FILE_WRITE_CHECK; + } + + // Remaining bytes in cache_fd is cached html. Fetch it and return it. + const long html_start_idx = ftell(cache_fd); + if (html_start_idx <= 0) { + fprintf( + stderr, + "WARNING Failed to get position in cache file, assuming " + "invalid/out-of-date!\n"); + force_cache_update = 1; + goto CACHE_FILE_WRITE_CHECK; + } + + ret = fseek(cache_fd, 0, SEEK_END); + if (ret != 0) { + fprintf( + stderr, + "WARNING Failed to seek in cache file, assuming invalid/out-of-date!\n"); + force_cache_update = 1; + goto CACHE_FILE_WRITE_CHECK; + } + const long html_end_idx = ftell(cache_fd); + if (html_end_idx <= 0) { + fprintf( + stderr, + "WARNING Failed to get end position in cache file, assuming " + "invalid/out-of-date!\n"); + force_cache_update = 1; + goto CACHE_FILE_WRITE_CHECK; + } + + ret = fseek(cache_fd, html_start_idx, SEEK_SET); + if (ret != 0) { + fprintf( + stderr, + "WARNING Failed to seek in cache file, assuming invalid/out-of-date!\n"); + force_cache_update = 1; + goto CACHE_FILE_WRITE_CHECK; + } + + const size_t html_size = (size_t)html_end_idx - (size_t)html_start_idx + 1; + *buf_out = malloc(html_size); + + if (fread(*buf_out, 1, html_size - 1, cache_fd) != html_size - 1) { + fprintf( + stderr, + "WARNING Failed to read html in cache file, assuming " + "invalid/out-of-date!\n"); + force_cache_update = 1; + goto CACHE_FILE_WRITE_CHECK; + } + + (*buf_out)[html_size - 1] = 0; + return 0; } diff --git a/src/html_cache.h b/src/html_cache.h index a382c34..95a36ab 100644 --- a/src/html_cache.h +++ b/src/html_cache.h @@ -17,21 +17,26 @@ #ifndef SEODISPARATE_COM_C_SIMPLE_HTTP_HTML_CACHE_H_ #define SEODISPARATE_COM_C_SIMPLE_HTTP_HTML_CACHE_H_ +// Local includes. +#include "http.h" + /// Must be free'd if non-NULL. char *c_simple_http_path_to_cache_filename(const char *path); /// Must be free'd if non-NULL. char *c_simple_http_cache_filename_to_path(const char *cache_filename); -/// Given a "path", returns non-zero if the cache is invalidated. +/// Given a "path", returns positive-non-zero if the cache is invalidated. /// "config_filename" is required to check its timestamp. "cache_dir" is /// required to actually get the cache file to check against. "buf_out" will be /// populated if non-NULL, and will either be fetched from the cache or from the /// config (using http_template). Note that "buf_out" will point to a c-string. +/// Returns a negative value on error. int c_simple_http_cache_path( const char *path, const char *config_filename, const char *cache_dir, + const C_SIMPLE_HTTP_HTTPTemplates *templates, char **buf_out); #endif diff --git a/src/http.c b/src/http.c index 0db0099..e32e54b 100644 --- a/src/http.c +++ b/src/http.c @@ -28,6 +28,7 @@ // Local includes #include "http_template.h" #include "helpers.h" +#include "html_cache.h" #define REQUEST_TYPE_BUFFER_SIZE 16 #define REQUEST_PATH_BUFFER_SIZE 256 @@ -63,7 +64,9 @@ char *c_simple_http_request_response( uint32_t size, const C_SIMPLE_HTTP_HTTPTemplates *templates, size_t *out_size, - enum C_SIMPLE_HTTP_ResponseCode *out_response_code) { + enum C_SIMPLE_HTTP_ResponseCode *out_response_code, + const char *cache_dir, + const char *config_filename) { if (out_size) { *out_size = 0; } @@ -171,11 +174,42 @@ char *c_simple_http_request_response( __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) char *stripped_path = c_simple_http_strip_path( request_path_unescaped, strlen(request_path_unescaped)); - char *generated_buf = c_simple_http_path_to_generated( - stripped_path ? stripped_path : request_path_unescaped, - templates, - &generated_size, - NULL); // TODO Use the output parameter "filenames list" here. + + char *generated_buf = NULL; + + if (cache_dir) { + int ret = c_simple_http_cache_path( + stripped_path ? stripped_path : request_path_unescaped, + config_filename, + cache_dir, + templates, + &generated_buf); + if (ret < 0) { + fprintf(stderr, "ERROR Failed to generate template with cache!\n"); + if (out_response_code) { + if ( + simple_archiver_hash_map_get( + templates->hash_map, + stripped_path ? stripped_path : request_path_unescaped, + stripped_path + ? strlen(stripped_path) + 1 + : strlen(request_path_unescaped) + 1) + == NULL) { + *out_response_code = C_SIMPLE_HTTP_Response_404_Not_Found; + } else { + *out_response_code = C_SIMPLE_HTTP_Response_500_Internal_Server_Error; + } + } + return NULL; + } + generated_size = strlen(generated_buf); + } else { + generated_buf = c_simple_http_path_to_generated( + stripped_path ? stripped_path : request_path_unescaped, + templates, + &generated_size, + NULL); + } if (!generated_buf || generated_size == 0) { fprintf(stderr, "ERROR Unable to generate response html for path \"%s\"!\n", diff --git a/src/http.h b/src/http.h index d050b27..725fbf8 100644 --- a/src/http.h +++ b/src/http.h @@ -48,7 +48,9 @@ char *c_simple_http_request_response( uint32_t size, const C_SIMPLE_HTTP_HTTPTemplates *templates, size_t *out_size, - enum C_SIMPLE_HTTP_ResponseCode *out_response_code + enum C_SIMPLE_HTTP_ResponseCode *out_response_code, + const char *cache_dir, + const char *config_filename ); /// Takes a PATH string and returns a "bare" path. diff --git a/src/main.c b/src/main.c index 7c65510..9c9f51c 100644 --- a/src/main.c +++ b/src/main.c @@ -359,7 +359,9 @@ int main(int argc, char **argv) { (uint32_t)read_ret, &parsed_config, &response_size, - &response_code); + &response_code, + args.cache_dir, + args.config_file); if (response && response_code == C_SIMPLE_HTTP_Response_200_OK) { CHECK_ERROR_WRITE(write(connection_fd, "HTTP/1.1 200 OK\n", 16)); CHECK_ERROR_WRITE(write(connection_fd, "Allow: GET\n", 11)); diff --git a/src/test.c b/src/test.c index e9a5da0..9c17dbe 100644 --- a/src/test.c +++ b/src/test.c @@ -4,6 +4,11 @@ #include #include +// POSIX includes. +#include +#include +#include + // Local includes. #include "config.h" #include "helpers.h" @@ -593,6 +598,19 @@ int main(void) { CHECK_TRUE(strcmp(buf, "ABC%ZZ") == 0); free(buf); buf = NULL; + + DIR *dirp = opendir("/tmp/create_dirs_dir"); + uint_fast8_t dir_exists = dirp ? 1 : 0; + closedir(dirp); + ASSERT_FALSE(dir_exists); + + int ret = c_simple_http_helper_mkdir_tree("/tmp/create_dirs_dir/dir/"); + int ret2 = rmdir("/tmp/create_dirs_dir/dir"); + int ret3 = rmdir("/tmp/create_dirs_dir"); + + CHECK_TRUE(ret == 0); + CHECK_TRUE(ret2 == 0); + CHECK_TRUE(ret3 == 0); } // Test html_cache. @@ -655,11 +673,11 @@ int main(void) { ret = c_simple_http_cache_filename_to_path("0x2Fouter0x2Finner"); ASSERT_TRUE(ret); - printf("%s\n", ret); CHECK_TRUE(strcmp(ret, "/outer/inner") == 0); free(ret); - ret = c_simple_http_cache_filename_to_path("0x2Fouter0x2Finner0x2F%2F0x2Fmore_inner"); + ret = c_simple_http_cache_filename_to_path( + "0x2Fouter0x2Finner0x2F%2F0x2Fmore_inner"); ASSERT_TRUE(ret); CHECK_TRUE(strcmp(ret, "/outer/inner/%2F/more_inner") == 0); free(ret); @@ -669,7 +687,8 @@ int main(void) { CHECK_TRUE(strcmp(ret, "/outer/inner") == 0); free(ret); - ret = c_simple_http_cache_filename_to_path("%2Fouter%2Finner%2F0x2F%2Fmore_inner"); + ret = c_simple_http_cache_filename_to_path( + "%2Fouter%2Finner%2F0x2F%2Fmore_inner"); ASSERT_TRUE(ret); CHECK_TRUE(strcmp(ret, "/outer/inner/0x2F/more_inner") == 0); free(ret); @@ -721,6 +740,172 @@ int main(void) { ASSERT_TRUE(ret2); CHECK_TRUE(strcmp(ret2, uri3) == 0); free(ret2); + + // Set up test config to get template map to test cache. + __attribute__((cleanup(test_internal_cleanup_delete_temporary_file))) + const char *test_http_template_filename5 = + "/tmp/c_simple_http_template_test5.config"; + __attribute__((cleanup(test_internal_cleanup_delete_temporary_file))) + const char *test_http_template_html_filename3 = + "/tmp/c_simple_http_template_test3.html"; + __attribute__((cleanup(test_internal_cleanup_delete_temporary_file))) + const char *test_http_template_html_var_filename2 = + "/tmp/c_simple_http_template_test_var2.html"; + + FILE *test_file = fopen(test_http_template_filename5, "w"); + ASSERT_TRUE(test_file); + + ASSERT_TRUE( + fwrite( + "PATH=/\nHTML_FILE=/tmp/c_simple_http_template_test3.html\n", + 1, + 56, + test_file) + == 56); + ASSERT_TRUE( + fwrite( + "VAR_FILE=/tmp/c_simple_http_template_test_var2.html\n", + 1, + 52, + test_file) + == 52); + fclose(test_file); + + test_file = fopen(test_http_template_html_filename3, "w"); + ASSERT_TRUE(test_file); + + ASSERT_TRUE( + fwrite( + "{{{VAR_FILE}}}\n", + 1, + 28, + test_file) + == 28); + fclose(test_file); + + test_file = fopen(test_http_template_html_var_filename2, "w"); + ASSERT_TRUE(test_file); + + ASSERT_TRUE( + fwrite( + "Some test text.
Yep.", + 1, + 23, + test_file) + == 23); + fclose(test_file); + + __attribute__((cleanup(c_simple_http_clean_up_parsed_config))) + C_SIMPLE_HTTP_ParsedConfig templates = + c_simple_http_parse_config(test_http_template_filename5, "PATH", NULL); + ASSERT_TRUE(templates.paths); + + // Run cache function. Should return >0 due to new/first cache entry. + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) + char *buf = NULL; + int int_ret = c_simple_http_cache_path( + "/", + test_http_template_filename5, + "/tmp/c_simple_http_cache_dir", + &templates, + &buf); + + CHECK_TRUE(int_ret > 0); + ASSERT_TRUE(buf); + CHECK_TRUE(strcmp(buf, "Some test text.
Yep.\n") == 0); + free(buf); + buf = NULL; + + // Check/get size of cache file. + FILE *cache_file = fopen("/tmp/c_simple_http_cache_dir/ROOT", "r"); + uint_fast8_t cache_file_exists = cache_file ? 1 : 0; + fseek(cache_file, 0, SEEK_END); + const long cache_file_size_0 = ftell(cache_file); + fclose(cache_file); + ASSERT_TRUE(cache_file_exists); + + // Re-run cache function, checking that it is not invalidated. + int_ret = c_simple_http_cache_path( + "/", + test_http_template_filename5, + "/tmp/c_simple_http_cache_dir", + &templates, + &buf); + CHECK_TRUE(int_ret == 0); + ASSERT_TRUE(buf); + CHECK_TRUE(strcmp(buf, "Some test text.
Yep.\n") == 0); + free(buf); + buf = NULL; + + // Check/get size of cache file. + cache_file = fopen("/tmp/c_simple_http_cache_dir/ROOT", "r"); + cache_file_exists = cache_file ? 1 : 0; + fseek(cache_file, 0, SEEK_END); + const long cache_file_size_1 = ftell(cache_file); + fclose(cache_file); + ASSERT_TRUE(cache_file_exists); + CHECK_TRUE(cache_file_size_0 == cache_file_size_1); + + // Change a file used by the template for PATH=/ . + test_file = fopen(test_http_template_html_var_filename2, "w"); + ASSERT_TRUE(test_file); + + ASSERT_TRUE( + fwrite( + "Alternate test text.
Yep.", + 1, + 28, + test_file) + == 28); + fclose(test_file); + + // Re-run cache function, checking that it is invalidated. + int_ret = c_simple_http_cache_path( + "/", + test_http_template_filename5, + "/tmp/c_simple_http_cache_dir", + &templates, + &buf); + CHECK_TRUE(int_ret > 0); + ASSERT_TRUE(buf); + CHECK_TRUE(strcmp(buf, "Alternate test text.
Yep.\n") == 0); + free(buf); + buf = NULL; + + // Get/check size of cache file. + cache_file = fopen("/tmp/c_simple_http_cache_dir/ROOT", "r"); + cache_file_exists = cache_file ? 1 : 0; + fseek(cache_file, 0, SEEK_END); + const long cache_file_size_2 = ftell(cache_file); + fclose(cache_file); + ASSERT_TRUE(cache_file_exists); + CHECK_TRUE(cache_file_size_0 != cache_file_size_2); + + // Re-run cache function, checking that it is not invalidated. + int_ret = c_simple_http_cache_path( + "/", + test_http_template_filename5, + "/tmp/c_simple_http_cache_dir", + &templates, + &buf); + CHECK_TRUE(int_ret == 0); + ASSERT_TRUE(buf); + CHECK_TRUE(strcmp(buf, "Alternate test text.
Yep.\n") == 0); + free(buf); + buf = NULL; + + // Get/check size of cache file. + cache_file = fopen("/tmp/c_simple_http_cache_dir/ROOT", "r"); + cache_file_exists = cache_file ? 1 : 0; + fseek(cache_file, 0, SEEK_END); + const long cache_file_size_3 = ftell(cache_file); + fclose(cache_file); + ASSERT_TRUE(cache_file_exists); + CHECK_TRUE(cache_file_size_2 == cache_file_size_3); + + // Cleanup. + remove("/tmp/c_simple_http_cache_dir/ROOT"); + rmdir("/tmp/c_simple_http_cache_dir"); } RETURN()