static struct mail_transaction_ext_intro prev_intro;
-static void dump_hdr(struct istream *input, uint64_t *modseq_r)
+static void dump_hdr(struct istream *input, uint64_t *modseq_r,
+ unsigned int *version_r)
{
struct mail_transaction_log_header hdr;
const unsigned char *data;
(unsigned long long)hdr.initial_modseq);
printf("compat flags = %x\n", hdr.compat_flags);
*modseq_r = hdr.initial_modseq;
+ *version_r = MAIL_TRANSACTION_LOG_HDR_VERSION(&hdr);
}
static const char *log_record_type(unsigned int type)
}
}
-static int dump_record(struct istream *input, uint64_t *modseq)
+static int dump_record(struct istream *input, uint64_t *modseq,
+ unsigned int version)
{
struct mail_transaction_header hdr;
unsigned int hdr_size;
}
uint64_t prev_modseq = *modseq;
- mail_transaction_update_modseq(&hdr, data, modseq);
+ mail_transaction_update_modseq(&hdr, data, modseq, version);
if (*modseq > prev_modseq)
printf(", modseq=%llu", (unsigned long long)*modseq);
printf("\n");
{
struct istream *input;
uint64_t modseq;
+ unsigned int version;
int ret;
input = i_stream_create_file(argv[1], (size_t)-1);
- dump_hdr(input, &modseq);
+ dump_hdr(input, &modseq, &version);
do {
T_BEGIN {
- ret = dump_record(input, &modseq);
+ ret = dump_record(input, &modseq, version);
} T_END;
} while (ret > 0);
i_stream_unref(&input);
return count;
}
+static bool
+transaction_flag_updates_have_non_internal(struct mail_index_transaction *t)
+{
+ struct mail_transaction_log_file *file = t->view->index->log->head;
+ const uint8_t internal_flags =
+ MAIL_INDEX_MAIL_FLAG_BACKEND | MAIL_INDEX_MAIL_FLAG_DIRTY;
+ const struct mail_index_flag_update *u;
+ const unsigned int hdr_version =
+ MAIL_TRANSACTION_LOG_HDR_VERSION(&file->hdr);
+
+ if (!MAIL_TRANSACTION_LOG_VERSION_HAVE(hdr_version, HIDE_INTERNAL_MODSEQS)) {
+ /* this check can be a bit racy if the call isn't done while
+ transaction log is locked. practically it won't matter
+ now though. */
+ return array_count(&t->updates) > 0;
+ }
+
+ array_foreach(&t->updates, u) {
+ uint8_t changed_flags = u->add_flags | u->remove_flags;
+
+ if ((changed_flags & ~internal_flags) != 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
uint64_t mail_index_transaction_get_highest_modseq(struct mail_index_transaction *t)
{
struct mail_transaction_log_file *file = t->view->index->log->head;
/* sorting may change the order of keyword_updates, */
new_highest_modseq++;
}
- if (array_is_created(&t->updates) && array_count(&t->updates) > 0)
+ if (array_is_created(&t->updates) &&
+ transaction_flag_updates_have_non_internal(t) > 0)
new_highest_modseq++;
if (array_is_created(&t->keyword_updates)) {
new_highest_modseq +=
buffer_append(ctx->output, &hdr, sizeof(hdr));
buffer_append(ctx->output, data, size);
- mail_transaction_update_modseq(&hdr, data, &ctx->new_highest_modseq);
+ mail_transaction_update_modseq(&hdr, data, &ctx->new_highest_modseq,
+ MAIL_TRANSACTION_LOG_HDR_VERSION(&ctx->log->head->hdr));
ctx->transaction_count++;
}
return 0;
}
+static bool
+flag_updates_have_non_internal(const struct mail_transaction_flag_update *u,
+ unsigned int count, unsigned int version)
+{
+ const uint8_t internal_flags =
+ MAIL_INDEX_MAIL_FLAG_BACKEND | MAIL_INDEX_MAIL_FLAG_DIRTY;
+
+ /* Hide internal flags from modseqs if the log file's version
+ is new enough. This allows upgrading without the modseqs suddenly
+ shrinking. */
+ if (!MAIL_TRANSACTION_LOG_VERSION_HAVE(version, HIDE_INTERNAL_MODSEQS))
+ return TRUE;
+
+ for (unsigned int i = 0; i < count; i++) {
+ uint8_t changed_flags = u->add_flags | u->remove_flags;
+
+ if ((changed_flags & ~internal_flags) != 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
void mail_transaction_update_modseq(const struct mail_transaction_header *hdr,
- const void *data, uint64_t *cur_modseq)
+ const void *data, uint64_t *cur_modseq,
+ unsigned int version)
{
uint32_t trans_size;
break;
}
case MAIL_TRANSACTION_APPEND:
- case MAIL_TRANSACTION_FLAG_UPDATE:
case MAIL_TRANSACTION_KEYWORD_UPDATE:
case MAIL_TRANSACTION_KEYWORD_RESET:
case MAIL_TRANSACTION_ATTRIBUTE_UPDATE:
/* these changes increase modseq */
*cur_modseq += 1;
break;
+ case MAIL_TRANSACTION_FLAG_UPDATE: {
+ const struct mail_transaction_flag_update *rec = data;
+ unsigned int count;
+
+ count = (trans_size - sizeof(*hdr)) / sizeof(*rec);
+ if (flag_updates_have_non_internal(rec, count, version))
+ *cur_modseq += 1;
+ break;
+ }
case MAIL_TRANSACTION_MODSEQ_UPDATE: {
const struct mail_transaction_modseq_update *rec, *end;
while (cur_offset < offset) {
if (log_get_synced_record(file, &cur_offset, &hdr) < 0)
return- 1;
- mail_transaction_update_modseq(hdr, hdr + 1, &cur_modseq);
+ mail_transaction_update_modseq(hdr, hdr + 1, &cur_modseq,
+ MAIL_TRANSACTION_LOG_HDR_VERSION(&file->hdr));
}
/* @UNSAFE: cache the value */
while (cur_offset < file->sync_offset) {
if (log_get_synced_record(file, &cur_offset, &hdr) < 0)
return -1;
- mail_transaction_update_modseq(hdr, hdr + 1, &cur_modseq);
+ mail_transaction_update_modseq(hdr, hdr + 1, &cur_modseq,
+ MAIL_TRANSACTION_LOG_HDR_VERSION(&file->hdr));
if (cur_modseq >= modseq)
break;
}
const void *data = hdr + 1;
int ret;
- mail_transaction_update_modseq(hdr, hdr + 1,
- &file->sync_highest_modseq);
+ mail_transaction_update_modseq(hdr, hdr + 1, &file->sync_highest_modseq,
+ MAIL_TRANSACTION_LOG_HDR_VERSION(&file->hdr));
if ((hdr->type & MAIL_TRANSACTION_EXTERNAL) == 0)
return 1;
const char *lock_reason);
void mail_transaction_update_modseq(const struct mail_transaction_header *hdr,
- const void *data, uint64_t *cur_modseq);
+ const void *data, uint64_t *cur_modseq,
+ unsigned int version);
int mail_transaction_log_file_get_highest_modseq_at(
struct mail_transaction_log_file *file,
uoff_t offset, uint64_t *highest_modseq_r);
ret = log_view_is_record_valid(file, hdr, data) ? 1 : -1;
} T_END;
if (ret > 0) {
- mail_transaction_update_modseq(hdr, data, &view->prev_modseq);
+ mail_transaction_update_modseq(hdr, data, &view->prev_modseq,
+ MAIL_TRANSACTION_LOG_HDR_VERSION(&file->hdr));
*hdr_r = hdr;
*data_r = data;
view->cur_offset += full_size;
{
struct mail_transaction_log_file *file = log->head;
+ if (file->hdr.major_version < MAIL_TRANSACTION_LOG_MAJOR_VERSION ||
+ (file->hdr.major_version == MAIL_TRANSACTION_LOG_MAJOR_VERSION &&
+ file->hdr.minor_version < MAIL_TRANSACTION_LOG_MINOR_VERSION)) {
+ /* upgrade immediately to a new log file format */
+ return TRUE;
+ }
+
if (file->sync_offset > log->index->log_rotate_max_size) {
/* file is too large, definitely rotate */
return TRUE;
#define MAIL_TRANSACTION_LOG_SUFFIX ".log"
#define MAIL_TRANSACTION_LOG_MAJOR_VERSION 1
-#define MAIL_TRANSACTION_LOG_MINOR_VERSION 2
+#define MAIL_TRANSACTION_LOG_MINOR_VERSION 3
#define MAIL_TRANSACTION_LOG_HEADER_MIN_SIZE 24
#define MAIL_TRANSACTION_LOG_VERSION_FULL(major, minor) \
#define MAIL_TRANSACTION_LOG_VERSION_COMPAT_FLAGS \
MAIL_TRANSACTION_LOG_VERSION_FULL(1, 2)
+#define MAIL_TRANSACTION_LOG_VERSION_HIDE_INTERNAL_MODSEQS \
+ MAIL_TRANSACTION_LOG_VERSION_FULL(1, 3)
struct mail_transaction_log_header {
uint8_t major_version;
void mail_transaction_update_modseq(const struct mail_transaction_header *hdr,
const void *data ATTR_UNUSED,
- uint64_t *cur_modseq)
+ uint64_t *cur_modseq,
+ unsigned int version ATTR_UNUSED)
{
if ((hdr->type & MAIL_TRANSACTION_EXPUNGE) != 0)
*cur_modseq += 1;
void mail_transaction_update_modseq(const struct mail_transaction_header *hdr ATTR_UNUSED,
const void *data ATTR_UNUSED,
- uint64_t *cur_modseq)
+ uint64_t *cur_modseq,
+ unsigned int version ATTR_UNUSED)
{
*cur_modseq += 1;
}