diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
commit | f7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch) | |
tree | a3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/lib-index/mail-cache-private.h | |
parent | Initial commit. (diff) | |
download | dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.tar.xz dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.zip |
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib-index/mail-cache-private.h')
-rw-r--r-- | src/lib-index/mail-cache-private.h | 421 |
1 files changed, 421 insertions, 0 deletions
diff --git a/src/lib-index/mail-cache-private.h b/src/lib-index/mail-cache-private.h new file mode 100644 index 0000000..c2fee17 --- /dev/null +++ b/src/lib-index/mail-cache-private.h @@ -0,0 +1,421 @@ +#ifndef MAIL_CACHE_PRIVATE_H +#define MAIL_CACHE_PRIVATE_H + +#include "file-dotlock.h" +#include "mail-index-private.h" +#include "mail-cache.h" + +#define MAIL_CACHE_MAJOR_VERSION 1 +#define MAIL_CACHE_MINOR_VERSION 1 + +#define MAIL_CACHE_LOCK_TIMEOUT 10 +#define MAIL_CACHE_LOCK_CHANGE_TIMEOUT 300 + +#define MAIL_CACHE_MAX_WRITE_BUFFER (1024*256) + +#define MAIL_CACHE_IS_UNUSABLE(cache) \ + ((cache)->hdr == NULL) + +struct mail_cache_header { + /* Major version is increased only when you can't have backwards + compatibility. If the field doesn't match MAIL_CACHE_MAJOR_VERSION, + don't even try to read it. */ + uint8_t major_version; + /* If this isn't the same as sizeof(uoff_t), the cache file can't be + safely used with the current implementation. */ + uint8_t compat_sizeof_uoff_t; + /* Minor version is increased when the file format changes in a + backwards compatible way. */ + uint8_t minor_version; + uint8_t unused; + + /* Unique index file ID, which must match the main index's indexid. + See mail_index_header.indexid. */ + uint32_t indexid; + /* Cache file sequence. Increased on every purge. This must match the + main index's reset_id for "cache" extension or the cache offsets + aren't valid. When creating the first cache file, use the current + UNIX timestamp as the file_seq. */ + uint32_t file_seq; + + /* Number of cache records that are linked inside the cache file, + instead of being directly pointed from the main index. */ + uint32_t continued_record_count; + + /* Number of messages cached in this file. This does not include + the continuation records. + + NOTE: <=v2.1 used this for hole offset, so we can't fully + rely on it */ + uint32_t record_count; + /* Currently unused. */ + uint32_t backwards_compat_used_file_size; + /* Number of already expunged messages that currently have cache + content in this file. */ + uint32_t deleted_record_count; + + /* Offset to the first mail_cache_header_fields. */ + uint32_t field_header_offset; +}; + +struct mail_cache_header_fields { + /* Offset to the updated version of this header. Use + mail_index_offset_to_uint32() to decode it. */ + uint32_t next_offset; + /* Full size of this header. */ + uint32_t size; + /* Number of fields in this header. */ + uint32_t fields_count; + +#if 0 + /* Last time the field was accessed. Not updated more often than + once a day. This field may be overwritten later on, which in theory + could cause reading to see a partially updated (corrupted) value. + Don't fully trust this field unless it was read while cache is + locked. */ + uint32_t last_used[fields_count]; + /* (uint32_t)-1 for variable sized fields */ + uint32_t size[fields_count]; + /* enum mail_cache_field_type */ + uint8_t type[fields_count]; + /* enum mail_cache_decision_type. This field can be overwritten + later on to update the caching decision. */ + uint8_t decision[fields_count]; + /* NUL-separated list of field names */ + char name[fields_count][]; +#endif +}; + +/* Macros to return offsets to the fields in mail_cache_header_fields. */ +#define MAIL_CACHE_FIELD_LAST_USED() \ + (sizeof(uint32_t) * 3) +#define MAIL_CACHE_FIELD_SIZE(count) \ + (MAIL_CACHE_FIELD_LAST_USED() + sizeof(uint32_t) * (count)) +#define MAIL_CACHE_FIELD_TYPE(count) \ + (MAIL_CACHE_FIELD_SIZE(count) + sizeof(uint32_t) * (count)) +#define MAIL_CACHE_FIELD_DECISION(count) \ + (MAIL_CACHE_FIELD_TYPE(count) + sizeof(uint8_t) * (count)) +#define MAIL_CACHE_FIELD_NAMES(count) \ + (MAIL_CACHE_FIELD_DECISION(count) + sizeof(uint8_t) * (count)) + +struct mail_cache_record { + uint32_t prev_offset; + uint32_t size; /* full record size, including this header */ + /* array of { uint32_t field; [ uint32_t size; ] { .. } } */ +}; + +struct mail_cache_field_private { + struct mail_cache_field field; + + /* Highest message UID whose cache field of this type have been + accessed within this session. This is used to track whether messages + are accessed in non-ascending order, which indicates an IMAP client + that doesn't have a local cache. That will result in the caching + decision to change from TEMP to YES. */ + uint32_t uid_highwater; + + /* Unused fields aren't written to cache file */ + bool used:1; + /* field.decision is pending a write to cache file header. If the + cache header is read from disk, don't overwrite it. */ + bool decision_dirty:1; +}; + +struct mail_cache { + struct mail_index *index; + struct event *event; + /* Registered "cache" extension ID */ + uint32_t ext_id; + + char *filepath; + int fd; + + struct dotlock_settings dotlock_settings; + struct file_lock *file_lock; + + /* Cache file's inode, device and size when it was last fstat()ed. */ + ino_t st_ino; + dev_t st_dev; + uoff_t last_stat_size; + + /* Used to avoid logging mmap() errors too rapidly. */ + time_t last_mmap_error_time; + + /* a) mmaping the whole file */ + void *mmap_base; + /* b) using file cache */ + struct file_cache *file_cache; + /* c) using small read() calls with MAIL_INDEX_OPEN_FLAG_SAVEONLY */ + uoff_t read_offset; + buffer_t *read_buf; + /* Size of the cache file as currently mapped to memory. Used for all + of a), b), and c). */ + size_t mmap_length; + /* mail_cache_map() increases this always. Used only for asserts. */ + unsigned int remap_counter; + /* Linked list of all cache views. */ + struct mail_cache_view *views; + + /* mmap_disable=no: hdr points to data / NULL when cache is invalid. + mmap_disable=yes: hdr points to hdr_ro_copy. this is needed because + cache invalidation can zero the data any time */ + const struct mail_cache_header *hdr; + struct mail_cache_header hdr_ro_copy; + /* hdr_copy gets updated when cache is locked and written when + unlocking and hdr_modified=TRUE */ + struct mail_cache_header hdr_copy; + /* If non-0, the offset for the last seen mail_cache_header_fields. + Used as a cache to avoid reading through multiple next_offset + pointers. */ + uint32_t last_field_header_offset; + + /* Memory pool used for permanent field allocations. Currently this + means mail_cache_field.name and field_name_hash. */ + pool_t field_pool; + /* Size of fields[] and field_file_map[] */ + unsigned int fields_count; + /* All the registered cache fields. */ + struct mail_cache_field_private *fields; + /* mail_cache_field.idx -> file-specific header index. The reverse + of this is file_field_map[]. */ + uint32_t *field_file_map; + /* mail_cache_field.name -> mail_cache_field.idx */ + HASH_TABLE(char *, void *) field_name_hash; /* name -> idx */ + + /* file-specific header index -> mail_cache_fields.idx. The reverse + of this is field_file_map[]. */ + unsigned int *file_field_map; + /* Size of file_field_map[] */ + unsigned int file_fields_count; + + /* mail_cache_purge_later() sets these values to trigger purging on + the next index sync. need_purge_file_seq is set to the current + cache file_seq. If at sync time the file_seq differs, it means + the cache was already purged and another purge isn't necessary. */ + uint32_t need_purge_file_seq; + /* Human-readable reason for purging. Used for debugging and events. */ + char *need_purge_reason; + + /* Cache has been opened (or it doesn't exist). */ + bool opened:1; + /* Cache has been locked with mail_cache_lock(). */ + bool locked:1; + /* TRUE if the last lock attempt failed. The next locking attempt will + be non-blocking to avoid unnecessarily waiting on a cache that has + been locked for a long time. Since cache isn't strictly required, + this could avoid unnecessarily long waits with some edge cases. */ + bool last_lock_failed:1; + /* cache->hdr_copy has been modified. This must be used only while + cache is locked. */ + bool hdr_modified:1; + /* At least one of the cache fields' last_used or cache decision has + changed. mail_cache_header_fields_update() will be used to overwrite + these to the latest mail_cache_header_fields. */ + bool field_header_write_pending:1; + /* Cache is currently being purged. */ + bool purging:1; + /* Access the cache file by reading as little as possible from it + (as opposed to mmap()ing it or using file-cache.h API to cache + larger parts of it). This is used with MAIL_INDEX_OPEN_FLAG_SAVEONLY + to avoid unnecessary cache reads. */ + bool map_with_read:1; +}; + +struct mail_cache_loop_track { + /* we're looping if size_sum > (max_offset-min_offset) */ + uoff_t min_offset, max_offset; + uoff_t size_sum; +}; + +struct mail_cache_missing_reason_cache { + uint32_t highest_checked_seq; + uint32_t highest_seq_with_cache; + + uint32_t reset_id; + uint32_t log_file_head_seq; + uoff_t log_file_head_offset; +}; + +struct mail_cache_view { + struct mail_cache *cache; + struct mail_cache_view *prev, *next; + struct mail_index_view *view, *trans_view; + + struct mail_cache_transaction_ctx *transaction; + /* mail_cache_add() has been called for some of the messages between + trans_seq1..trans_seq2 in an uncommitted transaction. Check also + the transaction contents when looking up cache fields for these + mails. */ + uint32_t trans_seq1, trans_seq2; + + /* Used to avoid infinite loops in case cache records point to each + others, causing a loop. FIXME: New cache files no longer support + overwriting existing data, so this could be removed and replaced + with a simple check that prev_offset is always smaller than the + current record's offset. */ + struct mail_cache_loop_track loop_track; + /* Used for optimizing mail_cache_get_missing_reason() */ + struct mail_cache_missing_reason_cache reason_cache; + + /* if cached_exists_buf[field] == cached_exists_value, it's cached. + this allows us to avoid constantly clearing the whole buffer. + it needs to be cleared only when cached_exists_value is wrapped. */ + buffer_t *cached_exists_buf; + uint8_t cached_exists_value; + uint32_t cached_exists_seq; + + /* mail_cache_view_update_cache_decisions() has been used to disable + updating cache decisions. */ + bool no_decision_updates:1; +}; + +/* mail_cache_lookup_iter_next() returns the next found field. */ +struct mail_cache_iterate_field { + /* mail_cache_field.idx */ + unsigned int field_idx; + /* Size of data */ + unsigned int size; + /* Cache field content in the field type-specific format */ + const void *data; + /* Offset to data in cache file */ + uoff_t offset; +}; + +struct mail_cache_lookup_iterate_ctx { + struct mail_cache_view *view; + /* This must match mail_cache.remap_counter or the iterator is + invalid. */ + unsigned int remap_counter; + /* Message sequence as given to mail_cache_lookup_iter_init() */ + uint32_t seq; + + /* Pointer to current cache record being iterated. This may point + to the cache file or uncommitted transaction. */ + const struct mail_cache_record *rec; + /* Iterator's current position in the cache record. Starts from + sizeof(mail_cache_record). */ + unsigned int pos; + /* Copy of rec->size */ + unsigned int rec_size; + /* Cache file offset to the beginning of rec, or 0 if it points to + an uncommitted transaction. */ + uint32_t offset; + + /* Used to loop through all changes in the uncommited transaction, + in case there are multiple changes to the same message. */ + unsigned int trans_next_idx; + + /* Cache has become unusable. Stop the iteration. */ + bool stop:1; + /* I/O error or lock timeout occurred during iteration. Normally there + is no locking during iteration, but it may happen while cache is + being purged to wait for the purging to finish before cache can be + accessed again. */ + bool failed:1; + /* Iteration has finished returning changes from uncommitted + transaction's in-memory buffer. */ + bool memory_appends_checked:1; + /* Iteration has finished returning changes from uncommitted + transaction that were already written to cache file, but not + to main index. */ + bool disk_appends_checked:1; + /* TRUE if the field index numbers in rec as the internal + mail_cache_field.idx (instead of the file-specific indexes). + This indicates that the rec points to uncommited transaction's + in-memory buffer. */ + bool inmemory_field_idx:1; +}; + +/* Explicitly lock the cache file. Returns -1 if error / timed out, + 1 if ok, 0 if cache is broken/doesn't exist */ +int mail_cache_lock(struct mail_cache *cache); +/* Flush pending header updates and unlock. Returns -1 if cache is / just got + corrupted, 0 if ok. */ +int mail_cache_flush_and_unlock(struct mail_cache *cache); +/* Unlock the cache without any header updates. */ +void mail_cache_unlock(struct mail_cache *cache); + +int mail_cache_write(struct mail_cache *cache, const void *data, size_t size, + uoff_t offset); +int mail_cache_append(struct mail_cache *cache, const void *data, size_t size, + uint32_t *offset); + +int mail_cache_header_fields_read(struct mail_cache *cache); +int mail_cache_header_fields_update(struct mail_cache *cache); +void mail_cache_header_fields_get(struct mail_cache *cache, buffer_t *dest); +int mail_cache_header_fields_get_next_offset(struct mail_cache *cache, + uint32_t *offset_r); +void mail_cache_expunge_count(struct mail_cache *cache, unsigned int count); + +uint32_t mail_cache_lookup_cur_offset(struct mail_index_view *view, + uint32_t seq, uint32_t *reset_id_r); +int mail_cache_get_record(struct mail_cache *cache, uint32_t offset, + const struct mail_cache_record **rec_r); +uint32_t mail_cache_get_first_new_seq(struct mail_index_view *view); + +/* Returns TRUE if offset..size area has been tracked before. + Returns FALSE if the area may or may not have been tracked before, + but we don't know for sure yet. */ +bool mail_cache_track_loops(struct mail_cache_loop_track *loop_track, + uoff_t offset, uoff_t size); + +/* Iterate through a message's cached fields. */ +void mail_cache_lookup_iter_init(struct mail_cache_view *view, uint32_t seq, + struct mail_cache_lookup_iterate_ctx *ctx_r); +/* Returns 1 if field was returned, 0 if end of fields, or -1 if error. + Note that this may trigger re-reading and reallocating cache fields. */ +int mail_cache_lookup_iter_next(struct mail_cache_lookup_iterate_ctx *ctx, + struct mail_cache_iterate_field *field_r); +const struct mail_cache_record * +mail_cache_transaction_lookup_rec(struct mail_cache_transaction_ctx *ctx, + unsigned int seq, + unsigned int *trans_next_idx); +bool mail_cache_transactions_have_changes(struct mail_cache *cache); + +/* Return data from the specified position in the cache file. Returns 1 if + successful, 0 if offset/size points outside the cache file, -1 if I/O + error. */ +int mail_cache_map(struct mail_cache *cache, size_t offset, size_t size, + const void **data_r); +/* Map the whole cache file into memory. Returns 1 if ok, 0 if corrupted + (and deleted), -1 if I/O error. */ +int mail_cache_map_all(struct mail_cache *cache); +void mail_cache_file_close(struct mail_cache *cache); +int mail_cache_reopen(struct mail_cache *cache); +int mail_cache_sync_reset_id(struct mail_cache *cache); + +/* Notify the decision handling code that field was looked up for seq. + This should be called even for fields that aren't currently in cache file. + This is used to update caching decisions for fields that already exist + in the cache file. */ +void mail_cache_decision_state_update(struct mail_cache_view *view, + uint32_t seq, unsigned int field); +const char *mail_cache_decision_to_string(enum mail_cache_decision_type dec); +struct event_passthrough * +mail_cache_decision_changed_event(struct mail_cache *cache, struct event *event, + unsigned int field); + +struct mail_cache_purge_drop_ctx { + struct mail_cache *cache; + time_t max_yes_downgrade_time; + time_t max_temp_drop_time; +}; +enum mail_cache_purge_drop_decision { + MAIL_CACHE_PURGE_DROP_DECISION_NONE, + MAIL_CACHE_PURGE_DROP_DECISION_DROP, + MAIL_CACHE_PURGE_DROP_DECISION_TO_TEMP, +}; +void mail_cache_purge_drop_init(struct mail_cache *cache, + const struct mail_index_header *hdr, + struct mail_cache_purge_drop_ctx *ctx_r); +enum mail_cache_purge_drop_decision +mail_cache_purge_drop_test(struct mail_cache_purge_drop_ctx *ctx, + unsigned int field); + +int mail_cache_expunge_handler(struct mail_index_sync_map_ctx *sync_ctx, + const void *data, void **sync_context); + +void mail_cache_set_syscall_error(struct mail_cache *cache, + const char *function) ATTR_COLD; + +#endif |