#ifndef DISFLUID_DISFLUID_CACHE_ENTRY_INCLUDED # define DISFLUID_DISFLUID_CACHE_ENTRY_INCLUDED MAYBE_UNUSED static struct disfluid_cache_entry *cache_entry_alloc (void); MAYBE_UNUSED static void cache_entry_free (struct disfluid_cache_entry *entry); MAYBE_UNUSED static struct disfluid_cache_entry *cache_entry_from_file_name (const char *file_name); MAYBE_UNUSED static struct disfluid_cache_entry *cache_entry_from_fd (int fd); MAYBE_UNUSED static struct disfluid_cache_entry *cache_entry_dup (const struct disfluid_cache_entry *entry); MAYBE_UNUSED static int cache_entry_save_other_file_name (const struct disfluid_cache_entry *entry, const char *file_name); MAYBE_UNUSED static int cache_entry_save_other_fd (const struct disfluid_cache_entry *entry, int fd); MAYBE_UNUSED static void cache_entry_set_request_date (struct disfluid_cache_entry *entry, const struct timespec *date); MAYBE_UNUSED static void cache_entry_set_response_date (struct disfluid_cache_entry *entry, const struct timespec *date); MAYBE_UNUSED static void cache_entry_invalidate (struct disfluid_cache_entry *entry); MAYBE_UNUSED static void cache_entry_set_invalidated (struct disfluid_cache_entry *entry, bool invalidated); MAYBE_UNUSED static void cache_entry_set_file_name (struct disfluid_cache_entry *entry, const char *file_name); MAYBE_UNUSED static void cache_entry_get_request_date (const struct disfluid_cache_entry *entry, struct timespec *date); MAYBE_UNUSED static void cache_entry_get_response_date (const struct disfluid_cache_entry *entry, struct timespec *date); MAYBE_UNUSED static bool cache_entry_invalidated (const struct disfluid_cache_entry *entry); MAYBE_UNUSED static char *cache_entry_get_file_name (const struct disfluid_cache_entry *entry); # include # include "disfluid-init.h" struct disfluid_cache_entry { char *file_name; /* Might be NULL */ int fd; /* Might be < 0, but must be >= 0 if there is a file_name. */ off_t start; off_t header_stop; struct timespec request_date; struct timespec response_date; bool invalidated; }; static struct disfluid_cache_entry * cache_entry_alloc (void) { ensure_init (); struct disfluid_cache_entry *ret = malloc (sizeof (struct disfluid_cache_entry)); if (ret == NULL) { abort (); } ret->file_name = NULL; ret->fd = -1; ret->start = 0; ret->header_stop = 0; ret->request_date.tv_sec = 0; ret->request_date.tv_nsec = 0; ret->response_date.tv_sec = 0; ret->response_date.tv_nsec = 0; ret->invalidated = false; return ret; } static void cache_entry_free (struct disfluid_cache_entry *entry) { if (entry != NULL) { free (entry->file_name); if (entry->fd >= 0) { close (entry->fd); } } free (entry); } static struct disfluid_cache_entry * cache_entry_from_file_name (const char *file_name) { int fd = open (file_name, O_RDONLY, S_IRUSR); if (fd < 0) { abort (); } struct disfluid_cache_entry *ret = cache_entry_from_fd (fd); if (ret == NULL) { abort (); } cache_entry_set_file_name (ret, file_name); return ret; } static struct disfluid_cache_entry * cache_entry_dup (const struct disfluid_cache_entry *entry) { struct disfluid_cache_entry *ret = cache_entry_alloc (); if (ret != NULL && entry->fd >= 0) { ret->fd = dup (entry->fd); if (ret->fd < 0) { cache_entry_free (ret); ret = NULL; } } if (ret != NULL && entry->file_name != NULL) { ret->file_name = strdup (entry->file_name); if ((ret->file_name == NULL) != (entry->file_name == NULL)) { cache_entry_free (ret); ret = NULL; } } if (ret != NULL) { ret->start = entry->start; ret->header_stop = entry->header_stop; ret->request_date.tv_sec = entry->request_date.tv_sec; ret->request_date.tv_nsec = entry->request_date.tv_nsec; ret->response_date.tv_sec = entry->response_date.tv_sec; ret->response_date.tv_nsec = entry->response_date.tv_nsec; ret->invalidated = entry->invalidated; } return ret; } static inline void cache_entry_string_append (char **str, size_t *max, size_t *n, size_t suffix_length, const char *suffix) { if (*n + suffix_length > *max) { *str = realloc (*str, 2 * *max); if (*str == NULL) { abort (); } *max *= 2; cache_entry_string_append (str, max, n, suffix_length, suffix); } else { memcpy (&((*str)[*n]), suffix, suffix_length); *n += suffix_length; } } static inline ssize_t cache_entry_read_next_line_aux (int fd, size_t *restrict buffer_max, size_t *restrict buffer_length, char **buffer, size_t checked) { while (checked + 1 < *buffer_length && ((*buffer)[checked] != '\r' || (*buffer)[checked + 1] != '\n')) { checked++; } if (checked + 1 < *buffer_length) { return checked + 2; } if (*buffer_length == *buffer_max) { *buffer = realloc (*buffer, 2 * *buffer_max); if (*buffer == NULL) { abort (); } *buffer_max *= 2; } assert (*buffer_max > *buffer_length); ssize_t n_added = read (fd, &((*buffer)[*buffer_length]), *buffer_max - *buffer_length); if (n_added < 0) { abort (); } else if (n_added == 0) { return *buffer_length; } else { assert (*buffer_length + n_added <= *buffer_max); *buffer_length += n_added; } return cache_entry_read_next_line_aux (fd, buffer_max, buffer_length, buffer, checked); } static inline void cache_entry_read_next_line (int fd, size_t *restrict buffer_max, size_t *restrict buffer_length, char **buffer, char **line, size_t *line_length) { ssize_t length = cache_entry_read_next_line_aux (fd, buffer_max, buffer_length, buffer, 0); if (length < 0) { abort (); } *line = malloc (length + 1); if (*line == NULL) { abort (); } *line_length = length; memcpy (*line, *buffer, length); (*line)[length] = '\0'; memmove (*buffer, &((*buffer)[length]), *buffer_length - length); *buffer_length -= length; } static inline void cache_entry_enumerate_header_lines (const struct disfluid_cache_entry *entry, void (*cb) (void *, const char *, const char *), void *context) { off_t start_offset = lseek (entry->fd, 0, SEEK_CUR); if (start_offset < 0) { abort (); } size_t buffer_max = 1; size_t buffer_length = 0; char *buffer = malloc (buffer_max); if (buffer == NULL) { abort (); } char *line; size_t line_length; while (true) { cache_entry_read_next_line (entry->fd, &buffer_max, &buffer_length, &buffer, &line, &line_length); if (line == NULL) { /* Memory error */ abort (); } if (STREQ (line, "\r\n")) { /* End of parse */ ssize_t undo = buffer_length; lseek (entry->fd, -undo, SEEK_CUR); free (buffer); free (line); return; } char *header_name = malloc (line_length + 1); if (header_name == NULL) { abort (); } size_t header_name_length = 0; for (header_name_length = 0; header_name_length < line_length && line[header_name_length] != ':'; header_name_length++) { if (line[header_name_length] >= 'A' && line[header_name_length] <= 'Z') { line[header_name_length] = (line[header_name_length] - 'A') + 'a'; } header_name[header_name_length] = line[header_name_length]; } header_name[header_name_length] = '\0'; if (line[header_name_length] != ':') { /* HTTP bad header, restore position */ lseek (entry->fd, start_offset, SEEK_SET); free (buffer); free (line); free (header_name); return; } header_name_length++; cb (context, header_name, &(line[header_name_length])); free (line); free (header_name); } } static inline void cache_entry_parse_timespec (const char *data, struct timespec *ts) { /* A timespec is represented as SSS.NNN where SSS is the number of seconds, and NNN the number of nanoseconds. */ while (*data == ' ' || *data == '\t' || *data == '\r' || *data == '\n') { data++; } struct timespec r = {.tv_sec = 0,.tv_nsec = 0 }; size_t n_seconds_digits = 0; for (n_seconds_digits = 0; data[n_seconds_digits] >= '0' && data[n_seconds_digits] <= '9'; n_seconds_digits++) { r.tv_sec *= 10; r.tv_sec += (data[n_seconds_digits] - '0'); } data += n_seconds_digits; const bool has_dot = (data[0] == '.'); if (has_dot) { data++; } int nano_digits[9] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; for (size_t i = 0; i < 9 && data[i] >= '0' && data[i] <= '9'; i++) { nano_digits[i] = (data[i] - '0'); } for (size_t i = 0; i < 9; i++) { r.tv_nsec *= 10; r.tv_nsec += nano_digits[i]; } ts->tv_sec = r.tv_sec; ts->tv_nsec = r.tv_nsec; } static inline void cache_entry_iterate_metadata (void *ctx, const char *header, const char *value) { struct disfluid_cache_entry *entry = ctx; if (STREQ (header, "request-date")) { cache_entry_parse_timespec (value, &(entry->request_date)); } else if (STREQ (header, "response-date")) { cache_entry_parse_timespec (value, &(entry->response_date)); } else if (STREQ (header, "invalidated")) { while (*value == ' ' || *value == '\t' || *value == '\r' || *value == '\n') { value++; } if (STREQ (value, "true\r\n")) { entry->invalidated = true; } else if (STREQ (value, "false\r\n")) { entry->invalidated = false; } } } static inline void cache_entry_scan (struct disfluid_cache_entry *entry) { entry->start = lseek (entry->fd, 0, SEEK_CUR); cache_entry_enumerate_header_lines (entry, cache_entry_iterate_metadata, entry); entry->header_stop = lseek (entry->fd, 0, SEEK_CUR); /* Reset the file descriptor position, in case it is shared */ lseek (entry->fd, entry->start, SEEK_SET); } static struct disfluid_cache_entry * cache_entry_from_fd (int fd) { struct disfluid_cache_entry *ret = cache_entry_alloc (); if (ret == NULL) { abort (); } ret->fd = dup (fd); if (ret->fd < 0) { abort (); } cache_entry_scan (ret); assert (ret->start >= 0); assert (ret->header_stop >= 0); if (ret->header_stop == ret->start) { /* Assume that reading failed. */ cache_entry_free (ret); ret = NULL; } return ret; } static int cache_entry_save_other_file_name (const struct disfluid_cache_entry *entry, const char *file_name) { int fd = open (file_name, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP); if (fd < 0) { return errno; } return cache_entry_save_other_fd (entry, fd); } static int cache_entry_save_other_fd (const struct disfluid_cache_entry *entry, int fd) { char *set_request_date; char *set_response_date; char *set_invalidated; char *meta_header; int status; status = asprintf (&set_request_date, "Request-Date: %lld.%09ld\r\n", (long long) entry->request_date.tv_sec, (long) entry->request_date.tv_nsec); if (status < 0) { abort (); } status = asprintf (&set_response_date, "Response-Date: %lld.%09ld\r\n", (long long) entry->response_date.tv_sec, (long) entry->response_date.tv_nsec); if (status < 0) { abort (); } if (entry->invalidated) { status = asprintf (&set_invalidated, "Invalidated: true\r\n"); if (status < 0) { abort (); } } else { set_invalidated = strdup (""); if (set_invalidated == NULL) { abort (); } } status = asprintf (&meta_header, "%s%s%s\r\n", set_request_date, set_response_date, set_invalidated); free (set_request_date); free (set_response_date); free (set_invalidated); if (status < 0) { abort (); } size_t n_written = 0; while (n_written < strlen (meta_header)) { ssize_t n_written_this_time = write (fd, &(meta_header[n_written]), strlen (meta_header) - n_written); if (n_written_this_time <= 0) { free (meta_header); return 2; } n_written += n_written_this_time; } free (meta_header); return 0; } static void cache_entry_set_request_date (struct disfluid_cache_entry *entry, const struct timespec *date) { entry->request_date.tv_sec = date->tv_sec; entry->request_date.tv_nsec = date->tv_nsec; } static void cache_entry_set_response_date (struct disfluid_cache_entry *entry, const struct timespec *date) { entry->response_date.tv_sec = date->tv_sec; entry->response_date.tv_nsec = date->tv_nsec; } static void cache_entry_invalidate (struct disfluid_cache_entry *entry) { cache_entry_set_invalidated (entry, true); } static void cache_entry_set_invalidated (struct disfluid_cache_entry *entry, bool invalidated) { entry->invalidated = invalidated; } static void cache_entry_set_file_name (struct disfluid_cache_entry *entry, const char *file_name) { free (entry->file_name); entry->file_name = NULL; if (file_name != NULL) { entry->file_name = strdup (file_name); if (entry->file_name == NULL) { abort (); } } } static void cache_entry_get_request_date (const struct disfluid_cache_entry *entry, struct timespec *date) { date->tv_sec = entry->request_date.tv_sec; date->tv_nsec = entry->request_date.tv_nsec; } static void cache_entry_get_response_date (const struct disfluid_cache_entry *entry, struct timespec *date) { date->tv_sec = entry->response_date.tv_sec; date->tv_nsec = entry->response_date.tv_nsec; } static bool cache_entry_invalidated (const struct disfluid_cache_entry *entry) { return entry->invalidated; } static char * cache_entry_get_file_name (const struct disfluid_cache_entry *entry) { if (entry->file_name == NULL) { return NULL; } char *ret = strdup (entry->file_name); if (ret == NULL) { abort (); } return ret; } #endif /* DISFLUID_DISFLUID_CACHE_ENTRY_INCLUDED */