diff options
author | Vivien Kraus <vivien@planete-kraus.eu> | 2023-03-15 20:08:53 +0100 |
---|---|---|
committer | Vivien Kraus <vivien@planete-kraus.eu> | 2023-03-15 20:56:35 +0100 |
commit | 13feab587ff754ed0a9e3e93639ecd5c43f3ec40 (patch) | |
tree | 3ffa0bf4bbb14cdc8b93b2edb6d53edffee1d888 /src/libdisfluid/disfluid-cache-entry.h | |
parent | 9bbdb37e12021eca17d84107449e367a999bd658 (diff) |
Rework the cache entry file API.
A cache entry does not hold a reference to a file descriptor
anymore. They are loaded in memory, and can be updated with a system
of backups.
Diffstat (limited to 'src/libdisfluid/disfluid-cache-entry.h')
-rw-r--r-- | src/libdisfluid/disfluid-cache-entry.h | 688 |
1 files changed, 288 insertions, 400 deletions
diff --git a/src/libdisfluid/disfluid-cache-entry.h b/src/libdisfluid/disfluid-cache-entry.h index 272c9e8..ae91390 100644 --- a/src/libdisfluid/disfluid-cache-entry.h +++ b/src/libdisfluid/disfluid-cache-entry.h @@ -7,20 +7,8 @@ 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); @@ -36,9 +24,8 @@ 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 int +cache_entry_load (struct disfluid_cache_entry *entry, const char *filename); MAYBE_UNUSED static void cache_entry_get_request_date (const struct disfluid_cache_entry *entry, @@ -51,9 +38,9 @@ cache_entry_get_response_date (const struct disfluid_cache_entry *entry, 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); +MAYBE_UNUSED static int +cache_entry_save (const struct disfluid_cache_entry *entry, + const char *filename); # include <assert.h> @@ -61,10 +48,6 @@ MAYBE_UNUSED static char *cache_entry_get_file_name (const struct 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; @@ -80,10 +63,6 @@ cache_entry_alloc (void) { 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; @@ -95,60 +74,15 @@ cache_entry_alloc (void) 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; @@ -158,391 +92,203 @@ cache_entry_dup (const struct disfluid_cache_entry *entry) return ret; } -static inline void -cache_entry_string_append (char **str, size_t *max, size_t *n, - size_t suffix_length, const char *suffix) +static void +cache_entry_set_request_date (struct disfluid_cache_entry *entry, + const struct timespec *date) { - 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; - } + entry->request_date.tv_sec = date->tv_sec; + entry->request_date.tv_nsec = date->tv_nsec; } -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) +static void +cache_entry_set_response_date (struct disfluid_cache_entry *entry, + const struct timespec *date) { - 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); + entry->response_date.tv_sec = date->tv_sec; + entry->response_date.tv_nsec = date->tv_nsec; } -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) +static void +cache_entry_invalidate (struct disfluid_cache_entry *entry) { - 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; + entry->invalidated = true; } -static inline void -cache_entry_enumerate_header_lines (const struct disfluid_cache_entry *entry, - void (*cb) (void *, const char *, - const char *), void *context) +static void +cache_entry_set_invalidated (struct disfluid_cache_entry *entry, + bool invalidated) { - 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); - } + entry->invalidated = invalidated; } -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; -} +/* The cache entry file is composed as: */ -static inline void -cache_entry_iterate_metadata (void *ctx, const char *header, - const char *value) +/* "disfluid c.entry" as the magic number on 16 bytes. */ +/* The request date on 12 bytes: 8 bytes, big endian, unsigned, for + the tv_sec field, and then 4 bytes, big endian, unsigned, for the + tv_nsec field (must be < 1e9). */ +/* The response date on 12 bytes. */ +/* On 1 byte: a value >= 128 if invalidated, < 128 if still valid. */ + +static int +cache_entry_really_read_n (int fd, size_t n, uint8_t * bytes, + bool *file_incomplete) { - 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")) + size_t n_read = 0; + *file_incomplete = false; + while (n_read < n) { - while (*value == ' ' || *value == '\t' || *value == '\r' - || *value == '\n') + ssize_t n_this_time = read (fd, &(bytes[n_read]), n - n_read); + if (n_this_time < 0) { - value++; + return 1; } - if (STREQ (value, "true\r\n")) + else if (n_this_time == 0) { - entry->invalidated = true; + *file_incomplete = true; + return 1; } - else if (STREQ (value, "false\r\n")) + else { - entry->invalidated = false; + assert (n_this_time > 0); + n_read += n_this_time; } } + return 0; } -static inline void -cache_entry_scan (struct disfluid_cache_entry *entry) +static int +cache_entry_read_magic (int fd, bool *magic_ok, bool *file_incomplete) { - 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); + uint8_t magic[16]; + *magic_ok = false; + *file_incomplete = false; + int err = cache_entry_really_read_n (fd, 16, magic, file_incomplete); + if (err) + { + return err; + } + if (memcmp (magic, "disfluid c.entry", 16) != 0) + { + *magic_ok = false; + return 1; + } + *magic_ok = true; + return 0; } -static struct disfluid_cache_entry * -cache_entry_from_fd (int fd) +static int +cache_entry_read_timespec (int fd, struct timespec *timespec, + bool *file_incomplete) { - struct disfluid_cache_entry *ret = cache_entry_alloc (); - if (ret == NULL) + uint8_t data[12]; + *file_incomplete = false; + int err = cache_entry_really_read_n (fd, 12, data, file_incomplete); + if (err) { - abort (); + return err; } - ret->fd = dup (fd); - if (ret->fd < 0) + timespec->tv_sec = 0; + timespec->tv_nsec = 0; + for (size_t i = 0; i < 8; i++) { - abort (); + timespec->tv_sec *= 256; + timespec->tv_sec += data[i]; } - cache_entry_scan (ret); - assert (ret->start >= 0); - assert (ret->header_stop >= 0); - if (ret->header_stop == ret->start) + for (size_t i = 8; i < 12; i++) { - /* Assume that reading failed. */ - cache_entry_free (ret); - ret = NULL; + timespec->tv_nsec *= 256; + timespec->tv_nsec += data[i]; } - return ret; + return 0; } static int -cache_entry_save_other_file_name (const struct disfluid_cache_entry *entry, - const char *file_name) +cache_entry_load_aux (struct disfluid_cache_entry *entry, + const char *filename, bool *file_incomplete) { - int fd = open (file_name, O_WRONLY | O_CREAT | O_TRUNC, - S_IRUSR | S_IWUSR | S_IRGRP); + *file_incomplete = false; + int fd = open (filename, O_RDONLY, S_IRUSR | S_IWUSR | S_IRGRP); if (fd < 0) { - return errno; + return -1; } - 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) + bool magic_ok = false; + struct timespec request_date, response_date; + bool invalidated; + int error = 0; + error = cache_entry_read_magic (fd, &magic_ok, file_incomplete); + if (error) { - abort (); + goto cleanup; } - 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) + if (!magic_ok) { - abort (); + error = -1; + goto cleanup; } - if (entry->invalidated) + error = cache_entry_read_timespec (fd, &request_date, file_incomplete); + if (error) { - status = asprintf (&set_invalidated, "Invalidated: true\r\n"); - if (status < 0) - { - abort (); - } + goto cleanup; } - else + error = cache_entry_read_timespec (fd, &response_date, file_incomplete); + if (error) { - set_invalidated = strdup (""); - if (set_invalidated == NULL) - { - abort (); - } + goto cleanup; } - 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) + uint8_t flags; + ssize_t n_flags_read = read (fd, &flags, 1); + if (n_flags_read == 0) { - abort (); + *file_incomplete = true; } - size_t n_written = 0; - while (n_written < strlen (meta_header)) + if (n_flags_read < 0) { - 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; + error = -1; + goto cleanup; } - free (meta_header); - return 0; + invalidated = ((flags & 0x80) != 0); + cache_entry_set_request_date (entry, &request_date); + cache_entry_set_response_date (entry, &response_date); + cache_entry_set_invalidated (entry, invalidated); +cleanup: + close (fd); + return error; } -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) +static char * +cache_entry_backup_file_name (const char *filename) { - entry->invalidated = invalidated; + char *backup_file = malloc (strlen (filename) + strlen ("~") + 1); + if (backup_file == NULL) + { + return NULL; + } + strcpy (backup_file, filename); + strcat (backup_file, "~"); + return backup_file; } -static void -cache_entry_set_file_name (struct disfluid_cache_entry *entry, - const char *file_name) +static int +cache_entry_load (struct disfluid_cache_entry *entry, const char *filename) { - free (entry->file_name); - entry->file_name = NULL; - if (file_name != NULL) - { - entry->file_name = strdup (file_name); - if (entry->file_name == NULL) + /* Try to read from filename. If the file does not exist, do not + even try to read a backup. If the file exists but is incomplete, + read the backup file. */ + bool file_incomplete = false; + int error = cache_entry_load_aux (entry, filename, &file_incomplete); + if (file_incomplete) + { + char *backup_file = cache_entry_backup_file_name (filename); + if (backup_file == NULL) { abort (); } + error = cache_entry_load_aux (entry, backup_file, &file_incomplete); + free (backup_file); } + return error; } static void @@ -567,19 +313,161 @@ 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) +static int +cache_entry_really_write_n (int fd, size_t n, const uint8_t * data) { - if (entry->file_name == NULL) + while (n != 0) { - return NULL; + ssize_t n_this_time = write (fd, data, n); + if (n_this_time < 0) + { + return -1; + } + else if (n_this_time == 0) + { + /* Impossible */ + abort (); + } + else + { + assert (n_this_time > 0); + n -= n_this_time; + data = &(data[n_this_time]); + } } - char *ret = strdup (entry->file_name); - if (ret == NULL) + return 0; +} + +static int +cache_entry_write_magic (int fd) +{ + static const uint8_t magic[16] = "disfluid c.entry"; + return cache_entry_really_write_n (fd, 16, magic); +} + +static int +cache_entry_write_timespec (int fd, const struct timespec *ts) +{ + uint8_t data[12]; + time_t tv_sec = ts->tv_sec; + long tv_nsec = ts->tv_nsec; + assert (tv_nsec >= 0); + for (size_t i = 8; i-- > 0;) + { + data[i] = tv_sec % 256; + tv_sec /= 256; + } + for (size_t i = 12; i-- > 8;) + { + data[i] = tv_nsec % 256; + tv_nsec /= 256; + } + return cache_entry_really_write_n (fd, 12, data); +} + +static int +cache_entry_save (const struct disfluid_cache_entry *entry, + const char *filename) +{ + /* To save a file: */ + /* 1. Lock it. */ + /* 2. Copy it to `filename~' with copy_file_range. */ + /* 3. Overwrite `filename'. */ + /* 4. Unlock it. */ + int error = 0; + int actual_fd, backup_fd; + actual_fd = open (filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP); + char *backup_file = cache_entry_backup_file_name (filename); + if (backup_file == NULL) { abort (); } - return ret; + backup_fd = + open (backup_file, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP); + free (backup_file); + if (actual_fd < 0 || backup_fd < 0) + { + error = -1; + goto cleanup; + } + struct flock lock = { + .l_type = F_WRLCK, + .l_whence = SEEK_SET, + .l_start = 0, + .l_len = 0 + }; + int lock_error = fcntl (actual_fd, F_SETLKW, &lock); + if (lock_error) + { + error = -2; + goto cleanup; + } + ssize_t n_copied = 0; + do + { + /* Copy by blocks of 2 MiB. I don’t care the size, I don’t have + to allocate it anyway. */ + n_copied = + copy_file_range (actual_fd, NULL, backup_fd, NULL, 2 * 1024 * 1024, + 0); + if (n_copied < 0) + { + error = -3; + goto cleanup; + } + } + while (n_copied != 0); + off_t to_start = lseek (actual_fd, SEEK_SET, 0); + if (to_start != 0) + { + error = -4; + goto cleanup; + } + if (ftruncate (actual_fd, 0) != 0) + { + error = -5; + goto cleanup; + } + assert (error == 0); + error = cache_entry_write_magic (actual_fd); + if (error) + { + error = -6; + goto cleanup; + } + error = cache_entry_write_timespec (actual_fd, &(entry->request_date)); + if (error) + { + error = -7; + goto cleanup; + } + error = cache_entry_write_timespec (actual_fd, &(entry->response_date)); + if (error) + { + error = -8; + goto cleanup; + } + uint8_t flags = 0; + if (entry->invalidated) + { + flags = 0x80; + } + error = cache_entry_really_write_n (actual_fd, 1, &flags); + if (error) + { + error = -9; + goto cleanup; + } +cleanup: + if (actual_fd >= 0) + { + close (actual_fd); + } + if (backup_fd >= 0) + { + close (backup_fd); + } + return error; } #endif /* DISFLUID_DISFLUID_CACHE_ENTRY_INCLUDED */ |