static int mail_transaction_log_refresh(struct mail_transaction_log *log, bool nfs_flush) { struct mail_transaction_log_file *file; struct stat st; i_assert(log->head != NULL); if (MAIL_TRANSACTION_LOG_FILE_IN_MEMORY(log->head)) return 0; if (nfs_flush && log->nfs_flush) nfs_flush_file_handle_cache(log->filepath); if (nfs_safe_stat(log->filepath, &st) < 0) { if (errno != ENOENT) { mail_index_file_set_syscall_error(log->index, log->filepath, "stat()"); return -1; } /* see if the whole directory got deleted */ if (nfs_safe_stat(log->index->dir, &st) < 0 && errno == ENOENT) { log->index->index_deleted = TRUE; return -1; } /* the file should always exist at this point. if it doesn't, someone deleted it manually while the index was open. try to handle this nicely by creating a new log file. */ file = log->head; if (mail_transaction_log_create(log, FALSE) < 0) return -1; i_assert(file->refcount > 0); file->refcount--; log->index->need_recreate = TRUE; return 0; } else if (log->head->st_ino == st.st_ino && CMP_DEV_T(log->head->st_dev, st.st_dev)) { /* NFS: log files get rotated to .log.2 files instead of being unlinked, so we don't bother checking if the existing file has already been unlinked here (in which case inodes could match but point to different files) */ return 0; } file = mail_transaction_log_file_alloc(log, log->filepath); if (mail_transaction_log_file_open(file, FALSE) <= 0) { mail_transaction_log_file_free(&file); return -1; } i_assert(!file->locked); if (--log->head->refcount == 0) mail_transaction_logs_clean(log); mail_transaction_log_set_head(log, file); return 0; }
static int mbox_file_open_latest(struct mbox_lock_context *ctx, int lock_type) { struct mbox_mailbox *mbox = ctx->mbox; struct stat st; if (ctx->checked_file || lock_type == F_UNLCK) return 0; if (mbox->mbox_fd != -1) { /* we could flush NFS file handle cache here if we wanted to be sure that the file is latest, but mbox files get rarely deleted and the flushing might cause errors (e.g. EBUSY for trying to flush a /var/mail mountpoint) */ if (nfs_safe_stat(mailbox_get_path(&mbox->box), &st) < 0) { if (errno == ENOENT) mailbox_set_deleted(&mbox->box); else mbox_set_syscall_error(mbox, "stat()"); return -1; } if (st.st_ino != mbox->mbox_ino || !CMP_DEV_T(st.st_dev, mbox->mbox_dev)) mbox_file_close(mbox); } if (mbox->mbox_fd == -1) { if (mbox_file_open(mbox) < 0) return -1; } ctx->checked_file = TRUE; return 0; }
static int maildir_stat(struct maildir_mailbox *mbox, const char *path, struct stat *st_r) { struct mailbox *box = &mbox->box; int i; for (i = 0;; i++) { if (nfs_safe_stat(path, st_r) == 0) return 0; if (errno != ENOENT || i == MAILDIR_DELETE_RETRY_COUNT) break; if (!maildir_set_deleted(box)) return -1; /* try again */ } mail_storage_set_critical(box->storage, "stat(%s) failed: %m", path); return -1; }
static int maildir_keywords_sync(struct maildir_keywords *mk) { struct istream *input; struct stat st; char *line, *p, *new_name; const char **strp; unsigned int idx; int fd; /* Remember that we rely on uidlist file locking in here. That's why we rely on stat()'s timestamp and don't bother handling ESTALE errors. */ if (mk->storage->set->mail_nfs_storage) { /* file is updated only by replacing it, no need to flush attribute cache */ nfs_flush_file_handle_cache(mk->path); } if (nfs_safe_stat(mk->path, &st) < 0) { if (errno == ENOENT) { maildir_keywords_clear(mk); mk->synced = TRUE; return 0; } mail_storage_set_critical(mk->storage, "stat(%s) failed: %m", mk->path); return -1; } if (st.st_mtime == mk->synced_mtime) { /* hasn't changed */ mk->synced = TRUE; return 0; } mk->synced_mtime = st.st_mtime; fd = open(mk->path, O_RDONLY); if (fd == -1) { if (errno == ENOENT) { maildir_keywords_clear(mk); mk->synced = TRUE; return 0; } mail_storage_set_critical(mk->storage, "open(%s) failed: %m", mk->path); return -1; } maildir_keywords_clear(mk); input = i_stream_create_fd(fd, 1024, FALSE); while ((line = i_stream_read_next_line(input)) != NULL) { p = strchr(line, ' '); if (p == NULL) { /* note that when converting .customflags file this case happens in the first line. */ continue; } *p++ = '\0'; if (str_to_uint(line, &idx) < 0 || idx >= MAILDIR_MAX_KEYWORDS || *p == '\0') { /* shouldn't happen */ continue; } /* save it */ new_name = p_strdup(mk->pool, p); hash_table_insert(mk->hash, new_name, POINTER_CAST(idx + 1)); strp = array_idx_modifiable(&mk->list, idx); *strp = new_name; } i_stream_destroy(&input); if (close(fd) < 0) { mail_storage_set_critical(mk->storage, "close(%s) failed: %m", mk->path); return -1; } mk->synced = TRUE; return 0; }
static int mail_transaction_log_file_create2(struct mail_transaction_log_file *file, int new_fd, bool reset, struct dotlock **dotlock) { struct mail_index *index = file->log->index; struct stat st; const char *path2; buffer_t *writebuf; int fd, ret; bool rename_existing, need_lock; need_lock = file->log->head != NULL && file->log->head->locked; if (fcntl(new_fd, F_SETFL, O_APPEND) < 0) { log_file_set_syscall_error(file, "fcntl(O_APPEND)"); return -1; } if (file->log->nfs_flush) { /* although we check also mtime and file size below, it's done only to fix broken log files. we don't bother flushing attribute cache just for that. */ nfs_flush_file_handle_cache(file->filepath); } /* log creation is locked now - see if someone already created it. note that if we're rotating, we need to keep the log locked until the file has been rewritten. and because fcntl() locks are stupid, if we go and open()+close() the file and we had it already opened, its locks are lost. so we use stat() to check if the file has been recreated, although it almost never is. */ if (reset) rename_existing = FALSE; else if (nfs_safe_stat(file->filepath, &st) < 0) { if (errno != ENOENT) { log_file_set_syscall_error(file, "stat()"); return -1; } rename_existing = FALSE; } else if (st.st_ino == file->st_ino && CMP_DEV_T(st.st_dev, file->st_dev) && /* inode/dev checks are enough when we're rotating the file, but not when we're replacing a broken log file */ st.st_mtime == file->last_mtime && (uoff_t)st.st_size == file->last_size) { /* no-one else recreated the file */ rename_existing = TRUE; } else { /* recreated. use the file if its header is ok */ fd = nfs_safe_open(file->filepath, O_RDWR | O_APPEND); if (fd == -1) { if (errno != ENOENT) { log_file_set_syscall_error(file, "open()"); return -1; } } else { file->fd = fd; file->last_size = 0; if (mail_transaction_log_file_read_hdr(file, FALSE) > 0 && mail_transaction_log_file_stat(file, FALSE) == 0) { /* yes, it was ok */ file_dotlock_delete(dotlock); mail_transaction_log_file_add_to_list(file); return 0; } file->fd = -1; if (close(fd) < 0) log_file_set_syscall_error(file, "close()"); } rename_existing = FALSE; } if (index->fd == -1 && !rename_existing) { /* creating the initial index */ reset = TRUE; } if (mail_transaction_log_init_hdr(file->log, &file->hdr) < 0) return -1; if (reset) { /* don't reset modseqs. if we're reseting due to rebuilding indexes we'll probably want to keep uidvalidity and in such cases we really don't want to shrink modseqs. */ file->hdr.prev_file_seq = 0; file->hdr.prev_file_offset = 0; } writebuf = buffer_create_dynamic(pool_datastack_create(), 128); buffer_append(writebuf, &file->hdr, sizeof(file->hdr)); if (index->ext_hdr_init_data != NULL && reset) log_write_ext_hdr_init_data(index, writebuf); if (write_full(new_fd, writebuf->data, writebuf->used) < 0) { log_file_set_syscall_error(file, "write_full()"); return -1; } if (file->log->index->fsync_mode == FSYNC_MODE_ALWAYS) { /* the header isn't important, so don't bother calling fdatasync() unless it's required */ if (fdatasync(new_fd) < 0) { log_file_set_syscall_error(file, "fdatasync()"); return -1; } } file->fd = new_fd; ret = mail_transaction_log_file_stat(file, FALSE); if (need_lock) { /* we'll need to preserve the lock */ if (mail_transaction_log_file_lock(file) < 0) ret = -1; } /* if we return -1 the dotlock deletion code closes the fd */ file->fd = -1; if (ret < 0) return -1; /* keep two log files */ if (rename_existing) { /* rename() would be nice and easy way to do this, except then there's a race condition between the rename and file_dotlock_replace(). during that time the log file doesn't exist, which could cause problems. */ path2 = t_strconcat(file->filepath, ".2", NULL); if (i_unlink_if_exists(path2) < 0) { /* try to link() anyway */ } if (nfs_safe_link(file->filepath, path2, FALSE) < 0 && errno != ENOENT && errno != EEXIST) { mail_index_set_error(index, "link(%s, %s) failed: %m", file->filepath, path2); /* ignore the error. we don't care that much about the second log file and we're going to overwrite this first one. */ } /* NOTE: here's a race condition where both .log and .log.2 point to the same file. our reading code should ignore that though by comparing the inodes. */ } if (file_dotlock_replace(dotlock, DOTLOCK_REPLACE_FLAG_DONT_CLOSE_FD) <= 0) return -1; /* success */ file->fd = new_fd; mail_transaction_log_file_add_to_list(file); i_assert(!need_lock || file->locked); return 1; }