Mercurial > dovecot > core-2.2
view src/lib-index/mail-custom-flags.c @ 903:fd8888f6f037 HEAD
Naming style changes, finally got tired of most of the typedefs. Also the
previous enum -> macro change reverted so that we don't use the highest bit
anymore, that's incompatible with old indexes so they will be rebuilt.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Sun, 05 Jan 2003 15:09:51 +0200 |
parents | 553f050c8313 |
children | 2512ad6fd9e9 |
line wrap: on
line source
/* Copyright (C) 2002 Timo Sirainen */ #include "lib.h" #include "file-lock.h" #include "mmap-util.h" #include "write-full.h" #include "imap-util.h" #include "mail-index.h" #include "mail-index-util.h" #include "mail-custom-flags.h" #include <unistd.h> #include <fcntl.h> #include <ctype.h> /* Header is simply a counter which is increased every time the file is updated. This allows other processes to easily notice if there's been any changes. */ #define COUNTER_SIZE 4 #define HEADER_SIZE (COUNTER_SIZE + 1) /* 0000\n */ struct mail_custom_flags { struct mail_index *index; char *filepath; int fd; int lock_type; char sync_counter[COUNTER_SIZE]; char *custom_flags[MAIL_CUSTOM_FLAGS_COUNT]; void *mmap_base; size_t mmap_length; unsigned int syncing:1; unsigned int noupdate:1; unsigned int changed:1; }; static int lock_file(struct mail_custom_flags *mcf, int type); static int index_cf_set_syscall_error(struct mail_custom_flags *mcf, const char *function) { i_assert(function != NULL); index_set_error(mcf->index, "%s failed with custom flags file %s: %m", function, mcf->filepath); return FALSE; } static int update_mmap(struct mail_custom_flags *mcf) { if (mcf->mmap_base != NULL) { if (munmap(mcf->mmap_base, mcf->mmap_length) < 0) index_cf_set_syscall_error(mcf, "munmap()"); } mcf->mmap_base = mmap_rw_file(mcf->fd, &mcf->mmap_length); if (mcf->mmap_base == MAP_FAILED) { mcf->mmap_base = NULL; return index_cf_set_syscall_error(mcf, "mmap()"); } (void)madvise(mcf->mmap_base, mcf->mmap_length, MADV_SEQUENTIAL); return TRUE; } static int custom_flags_init(struct mail_custom_flags *mcf) { static char buf[HEADER_SIZE] = "0000\n"; int failed; off_t pos; if (!lock_file(mcf, F_WRLCK)) return FALSE; failed = FALSE; /* make sure it's still empty after locking */ pos = lseek(mcf->fd, 0, SEEK_END); if (pos >= 0 && pos < HEADER_SIZE) pos = lseek(mcf->fd, 0, SEEK_SET); if (pos < 0) { index_cf_set_syscall_error(mcf, "lseek()"); failed = TRUE; } /* write the header - it's a 4 byte counter as hex */ if (!failed && write_full(mcf->fd, buf, HEADER_SIZE) < 0) { if (errno == ENOSPC) mcf->index->nodiskspace = TRUE; index_cf_set_syscall_error(mcf, "write_full()"); failed = TRUE; } if (!lock_file(mcf, F_UNLCK)) return FALSE; return !failed; } static void custom_flags_sync(struct mail_custom_flags *mcf) { char *data, *data_end, *line; unsigned int num; int i; if (mcf->noupdate) return; memcpy(mcf->sync_counter, mcf->mmap_base, COUNTER_SIZE); for (i = 0; i < MAIL_CUSTOM_FLAGS_COUNT; i++) { if (mcf->custom_flags[i] != NULL) { i_free(mcf->custom_flags[i]); mcf->custom_flags[i] = NULL; } } data = mcf->mmap_base; data_end = data + mcf->mmap_length; /* this loop skips the first line, which is the header */ while (data != data_end) { if (*data != '\n') { data++; continue; } /* beginning of line, get the index */ if (data+1 == data_end) break; data++; if (!i_isdigit(*data)) continue; num = 0; while (data != data_end && *data >= '0' && *data <= '9') { num = num*10 + (*data-'0'); data++; } if (num < MAIL_CUSTOM_FLAGS_COUNT) { /* get the name */ if (data == data_end || *data != ' ') continue; line = ++data; while (data != data_end && *data != '\n') data++; if (mcf->custom_flags[num] != NULL) { i_warning("Error in custom flags file %s: " "Duplicated ID %u", mcf->filepath, num); i_free(mcf->custom_flags[num]); } mcf->custom_flags[num] = i_strdup_until(line, data); } } } static int custom_flags_check_sync(struct mail_custom_flags *mcf) { if (mcf->noupdate) return TRUE; if (mcf->mmap_length != 0 && memcmp(mcf->sync_counter, mcf->mmap_base, COUNTER_SIZE) == 0) return TRUE; /* file modified, resync */ if (!update_mmap(mcf)) return FALSE; if (mcf->mmap_length < HEADER_SIZE) { /* it's broken, rewrite header */ if (mcf->lock_type == F_RDLCK) (void)lock_file(mcf, F_UNLCK); if (!custom_flags_init(mcf)) return FALSE; if (!update_mmap(mcf)) return FALSE; } custom_flags_sync(mcf); mcf->changed = TRUE; return TRUE; } static int lock_file(struct mail_custom_flags *mcf, int type) { if (mcf->lock_type == type) return TRUE; /* FIXME: possibility to use .lock file instead */ if (file_wait_lock(mcf->fd, type) <= 0) return index_cf_set_syscall_error(mcf, "file_wait_lock()"); mcf->lock_type = type; if (type != F_UNLCK && !mcf->syncing) { mcf->syncing = TRUE; if (!custom_flags_check_sync(mcf)) { mcf->syncing = FALSE; return FALSE; } /* syncing may have changed locking, do it again */ if (!lock_file(mcf, type)) { mcf->syncing = FALSE; return FALSE; } mcf->syncing = FALSE; } return TRUE; } int mail_custom_flags_open_or_create(struct mail_index *index) { struct mail_custom_flags *mcf; const char *path; int fd; path = t_strconcat(index->dir, "/", CUSTOM_FLAGS_FILE_NAME, NULL); fd = open(path, O_RDWR | O_CREAT, 0660); if (fd == -1) { if (errno == ENOSPC) index->nodiskspace = TRUE; return index_file_set_syscall_error(index, path, "open()"); } mcf = i_new(struct mail_custom_flags, 1); mcf->index = index; mcf->filepath = i_strdup(path); mcf->fd = fd; if (!update_mmap(mcf)) { mail_custom_flags_free(mcf); return FALSE; } if (mcf->mmap_length < HEADER_SIZE) { /* we just created it, write the header */ mcf->syncing = TRUE; if ((!custom_flags_init(mcf) || !update_mmap(mcf)) && !index->nodiskspace) { mail_custom_flags_free(mcf); return FALSE; } mcf->syncing = FALSE; mcf->noupdate = index->nodiskspace; } custom_flags_sync(mcf); index->custom_flags = mcf; return TRUE; } void mail_custom_flags_free(struct mail_custom_flags *mcf) { int i; for (i = 0; i < MAIL_CUSTOM_FLAGS_COUNT; i++) i_free(mcf->custom_flags[i]); if (mcf->mmap_base != NULL) { if (munmap(mcf->mmap_base, mcf->mmap_length) < 0) index_cf_set_syscall_error(mcf, "munmap()"); } if (close(mcf->fd) < 0) index_cf_set_syscall_error(mcf, "close()"); i_free(mcf->filepath); i_free(mcf); } static int custom_flags_update_counter(struct mail_custom_flags *mcf) { int i; if (lseek(mcf->fd, 0, SEEK_SET) < 0) return index_cf_set_syscall_error(mcf, "lseek()"); for (i = COUNTER_SIZE-1; i >= 0; i--) { if (mcf->sync_counter[i] == '9') { mcf->sync_counter[i] = 'A'; break; } if (mcf->sync_counter[i] == 'F') { /* digit wrapped, update next one */ mcf->sync_counter[i] = '0'; } else { mcf->sync_counter[i]++; break; } } if (write_full(mcf->fd, mcf->sync_counter, COUNTER_SIZE) < 0) return index_cf_set_syscall_error(mcf, "write_full()"); mcf->changed = TRUE; return TRUE; } static int custom_flags_add(struct mail_custom_flags *mcf, int idx, const char *name) { const char *buf; size_t len; off_t pos; i_assert(idx < MAIL_CUSTOM_FLAGS_COUNT); /* first update the sync counter */ if (!custom_flags_update_counter(mcf)) return FALSE; /* add the flag */ pos = lseek(mcf->fd, 0, SEEK_END); if (pos < 0) return index_cf_set_syscall_error(mcf, "lseek()"); if (pos != (off_t)mcf->mmap_length) { index_set_error(mcf->index, "Custom flags file %s was " "changed by someone while we were" "trying to modify it", mcf->filepath); return FALSE; } buf = t_strdup_printf("\n%d %s\n", idx, name); len = strlen(buf); if (((char *) mcf->mmap_base)[mcf->mmap_length-1] == '\n') { /* don't add the \n prefix */ buf++; len--; } if (write_full(mcf->fd, buf, len) < 0) return index_cf_set_syscall_error(mcf, "write_full()"); if (!update_mmap(mcf)) return FALSE; return TRUE; } static int custom_flags_remove(struct mail_custom_flags *mcf, unsigned int idx) { char *data, *data_end, *line; unsigned int num; int pos, linelen; data = mcf->mmap_base; data_end = data + mcf->mmap_length; while (data != data_end) { if (*data != '\n') { data++; continue; } /* beginning of line, get the index */ if (data+1 == data_end) break; line = ++data; num = 0; while (data != data_end && *data >= '0' && *data <= '9') { num = num*10 + (*data-'0'); data++; } if (num == idx) { /* remove this line */ while (data != data_end && data[-1] != '\n') data++; linelen = (int) (data - line); pos = (int) (data - (char *) mcf->mmap_base); memmove(line, data, mcf->mmap_length - pos); mcf->mmap_length -= linelen; if (ftruncate(mcf->fd, (off_t) mcf->mmap_length) < 0) { index_cf_set_syscall_error(mcf, "ftruncate()"); return FALSE; } return TRUE; } } return FALSE; } static int find_first_unused_flag(struct mail_custom_flags *mcf) { int i; for (i = 0; i < MAIL_CUSTOM_FLAGS_COUNT; i++) { if (mcf->custom_flags[i] == NULL) return i; } return -1; } static void remove_unused_custom_flags(struct mail_custom_flags *mcf, enum mail_flags used_flags) { unsigned int i; for (i = 0; i < MAIL_CUSTOM_FLAGS_COUNT; i++) { if ((used_flags & (1 << (i + MAIL_CUSTOM_FLAG_1_BIT))) == 0) { i_free(mcf->custom_flags[i]); mcf->custom_flags[i] = NULL; custom_flags_remove(mcf, i); } } } static enum mail_flags get_used_flags(struct mail_custom_flags *mcf) { struct mail_index_record *rec; enum mail_flags used_flags; used_flags = 0; rec = mcf->index->lookup(mcf->index, 1); while (rec != NULL) { used_flags |= rec->msg_flags; rec = mcf->index->next(mcf->index, rec); } return used_flags; } static int get_flag_index(struct mail_custom_flags *mcf, const char *flag, int index_hint) { int i, first_empty; if (index_hint >= 0 && index_hint < MAIL_CUSTOM_FLAGS_COUNT) { if (mcf->custom_flags[index_hint] != NULL && strcasecmp(mcf->custom_flags[index_hint], flag) == 0) return index_hint; } /* check existing flags */ for (i = 0; i < MAIL_CUSTOM_FLAGS_COUNT; i++) { if (mcf->custom_flags[i] != NULL) { i_assert(mcf->custom_flags[i] != '\0'); if (strcasecmp(mcf->custom_flags[i], flag) == 0) return i; } } if (mcf->noupdate) return -1; if (mcf->lock_type != F_WRLCK) { /* unlock + write lock, don't directly change from read -> write lock to prevent deadlocking */ if (!lock_file(mcf, F_UNLCK) || !lock_file(mcf, F_WRLCK)) return -1; /* list may have already changed between the lock changes, check again */ return get_flag_index(mcf, flag, -1); } /* new flag, add it. first find the first free flag, note that unlock+lock might have just changed it. */ first_empty = find_first_unused_flag(mcf); if (first_empty == -1) { /* all custom flags are used, see if some of them are unused */ remove_unused_custom_flags(mcf, get_used_flags(mcf)); first_empty = find_first_unused_flag(mcf); if (first_empty == -1) { /* everything is in use */ return -1; } } if (!custom_flags_add(mcf, first_empty, flag)) return -1; mcf->index->set_flags |= MAIL_INDEX_FLAG_DIRTY_CUSTOMFLAGS; mcf->custom_flags[first_empty] = i_strdup(flag); return first_empty; } int mail_custom_flags_fix_list(struct mail_custom_flags *mcf, enum mail_flags *flags, const char *custom_flags[], unsigned int count) { enum mail_flags oldflags, flag; int i, idx; i_assert(count < 32); if ((*flags & MAIL_CUSTOM_FLAGS_MASK) == 0) return 1; if (!lock_file(mcf, F_RDLCK)) return -1; oldflags = *flags; *flags &= MAIL_SYSTEM_FLAGS_MASK; flag = MAIL_CUSTOM_FLAG_1; for (i = 0; i < (int)count; i++, flag <<= 1) { if ((oldflags & flag) && custom_flags[i] != NULL) { i_assert(*custom_flags[i] != '\0'); idx = get_flag_index(mcf, custom_flags[i], i); if (idx == -1) { (void)lock_file(mcf, F_UNLCK); return 0; } *flags |= 1 << (idx + MAIL_CUSTOM_FLAG_1_BIT); } } if (!lock_file(mcf, F_UNLCK)) return -1; return 1; } const char **mail_custom_flags_list_get(struct mail_custom_flags *mcf) { return (const char **) mcf->custom_flags; } int mail_custom_flags_has_changes(struct mail_custom_flags *mcf) { if (!mcf->changed) return FALSE; else { mcf->changed = FALSE; return TRUE; } }