view src/lib-index/mailbox-log.c @ 12262:80a080814041

lib-index: Mailbox log writing was trying to write to a closed log file fd.
author Timo Sirainen <tss@iki.fi>
date Thu, 14 Oct 2010 16:17:43 +0100
parents 2b8b2875af26
children 03ac8057710d
line wrap: on
line source

/* Copyright (c) 2009-2010 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "ioloop.h"
#include "eacces-error.h"
#include "mailbox-log.h"

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

#define MAILBOX_LOG_ROTATE_SIZE (1024*4)

struct mailbox_log {
	char *filepath, *filepath2;
	int fd;
	time_t open_timestamp;

	mode_t mode;
	gid_t gid;
	char *gid_origin;
};

struct mailbox_log_iter {
	struct mailbox_log *log;

	int fd;
	const char *filepath;

	struct mailbox_log_record buf[128];
	unsigned int idx, count;
	uoff_t offset;
	bool failed;
};

static void mailbox_log_close(struct mailbox_log *log);

struct mailbox_log *mailbox_log_alloc(const char *path)
{
	struct mailbox_log *log;

	log = i_new(struct mailbox_log, 1);
	log->filepath = i_strdup(path);
	log->filepath2 = i_strconcat(path, ".2", NULL);
	log->mode = 0644;
	log->gid = (gid_t)-1;
	log->fd = -1;
	return log;
}

void mailbox_log_free(struct mailbox_log **_log)
{
	struct mailbox_log *log = *_log;

	*_log = NULL;

	mailbox_log_close(log);
	i_free(log->gid_origin);
	i_free(log->filepath);
	i_free(log->filepath2);
	i_free(log);
}

static void mailbox_log_close(struct mailbox_log *log)
{
	if (log->fd != -1) {
		if (close(log->fd) < 0)
			i_error("close(%s) failed: %m", log->filepath);
		log->fd = -1;
	}
}

void mailbox_log_set_permissions(struct mailbox_log *log, mode_t mode,
				 gid_t gid, const char *gid_origin)
{
	log->mode = mode;
	log->gid = gid;
	i_free(log->gid_origin);
	log->gid_origin = i_strdup(gid_origin);
}

static int mailbox_log_open(struct mailbox_log *log)
{
	mode_t old_mode;

	i_assert(log->fd == -1);

	log->open_timestamp = ioloop_time;
	log->fd = open(log->filepath, O_RDWR | O_APPEND);
	if (log->fd != -1)
		return 0;

	/* try to create it */
	old_mode = umask(0666 ^ log->mode);
	log->fd = open(log->filepath, O_RDWR | O_APPEND | O_CREAT, 0666);
	umask(old_mode);

	if (log->fd == -1) {
		if (errno != EACCES)
			i_error("creat(%s) failed: %m", log->filepath);
		else
			i_error("%s", eacces_error_get("creat", log->filepath));
		return -1;
	}
	if (fchown(log->fd, (uid_t)-1, log->gid) < 0) {
		if (errno != EPERM)
			i_error("fchown(%s) failed: %m", log->filepath);
		else {
			i_error("%s", eperm_error_get_chgrp("fchown",
						log->filepath, log->gid,
						log->gid_origin));
		}
	}
	return 0;
}

static int mailbox_log_rotate_if_needed(struct mailbox_log *log)
{
	struct stat st;

	if (fstat(log->fd, &st) < 0) {
		i_error("fstat(%s) failed: %m", log->filepath);
		return -1;
	}

	if (st.st_size < MAILBOX_LOG_ROTATE_SIZE)
		return 0;

	if (rename(log->filepath, log->filepath2) < 0 && errno != ENOENT) {
		i_error("rename(%s, %s) failed: %m",
			log->filepath, log->filepath2);
		return -1;
	}
	return 0;
}

void mailbox_log_record_set_timestamp(struct mailbox_log_record *rec,
				      time_t stamp)
{
	rec->timestamp[0] = (stamp & 0xff000000) >> 24;
	rec->timestamp[1] = (stamp & 0x00ff0000) >> 16;
	rec->timestamp[2] = (stamp & 0x0000ff00) >> 8;
	rec->timestamp[3] = (stamp & 0x000000ff);
}

time_t mailbox_log_record_get_timestamp(const struct mailbox_log_record *rec)
{
	return ((time_t)rec->timestamp[0] << 24) |
		((time_t)rec->timestamp[1] << 16) |
		((time_t)rec->timestamp[2] << 8) |
		(time_t)rec->timestamp[3];
}

int mailbox_log_append(struct mailbox_log *log,
		       const struct mailbox_log_record *rec)
{
	struct stat st;
	ssize_t ret;

	/* we don't have to be too strict about appending to the latest log
	   file. the records' ordering doesn't matter and iteration goes
	   through both logs anyway. still, if there's a long running session
	   it shouldn't keep writing to a rotated log forever. */
	if (log->open_timestamp != ioloop_time)
		mailbox_log_close(log);
	if (log->fd == -1) {
		if (mailbox_log_open(log) < 0)
			return -1;
	}

	/* We don't bother with locking, atomic appends will protect us.
	   If they don't (NFS), the worst that can happen is that a few
	   records get overwritten (because they're all the same size).
	   This whole log isn't supposed to be super-reliable anyway. */
	ret = write(log->fd, rec, sizeof(*rec));
	if (ret < 0) {
		i_error("write(%s) failed: %m", log->filepath);
		return -1;
	} else if (ret != sizeof(*rec)) {
		i_error("write(%s) wrote %d/%u bytes", log->filepath,
			(int)ret, (unsigned int)sizeof(*rec));
		if (fstat(log->fd, &st) == 0) {
			if (ftruncate(log->fd, st.st_size - ret) < 0) {
				i_error("ftruncate(%s) failed: %m",
					log->filepath);
			}
		}
		return -1;
	}

	(void)mailbox_log_rotate_if_needed(log);
	return 0;
}

static bool mailbox_log_iter_open_next(struct mailbox_log_iter *iter)
{
	if (iter->fd != -1) {
		if (close(iter->fd) < 0)
			i_error("close(%s) failed: %m", iter->filepath);
		iter->fd = -1;
	}
	if (iter->filepath == NULL)
		iter->filepath = iter->log->filepath2;
	else if (iter->filepath == iter->log->filepath2)
		iter->filepath = iter->log->filepath;
	else
		return FALSE;

	iter->fd = open(iter->filepath, O_RDONLY | O_APPEND);
	if (iter->fd != -1)
		return TRUE;
	else if (errno == ENOENT) {
		if (iter->filepath == iter->log->filepath2)
			return mailbox_log_iter_open_next(iter);
	} else {
		i_error("open(%s) failed: %m", iter->filepath);
		iter->failed = TRUE;
	}
	return FALSE;
}

struct mailbox_log_iter *mailbox_log_iter_init(struct mailbox_log *log)
{
	struct mailbox_log_iter *iter;

	iter = i_new(struct mailbox_log_iter, 1);
	iter->log = log;
	iter->fd = -1;
	(void)mailbox_log_iter_open_next(iter);
	return iter;
}

const struct mailbox_log_record *
mailbox_log_iter_next(struct mailbox_log_iter *iter)
{
	const struct mailbox_log_record *rec;
	uoff_t offset;
	ssize_t ret;

	if (iter->idx == iter->count) {
		if (iter->fd == -1)
			return NULL;

		ret = pread(iter->fd, iter->buf, sizeof(iter->buf),
			    iter->offset);
		if (ret < 0) {
			i_error("pread(%s) failed: %m", iter->filepath);
			iter->failed = TRUE;
			return NULL;
		}
		if (ret == 0) {
			if (!mailbox_log_iter_open_next(iter))
				return NULL;
			iter->idx = iter->count = 0;
			iter->offset = 0;
			return mailbox_log_iter_next(iter);
		}
		iter->idx = 0;
		iter->count = ret / sizeof(iter->buf[0]);
		iter->offset += iter->count * sizeof(iter->buf[0]);
	}
	rec = &iter->buf[iter->idx++];
	if (rec->type < MAILBOX_LOG_RECORD_DELETE_MAILBOX ||
	    rec->type > MAILBOX_LOG_RECORD_UNSUBSCRIBE) {
		offset = iter->offset -
			(iter->count - iter->idx) * sizeof(iter->buf[0]);
		i_error("Corrupted mailbox log %s at offset %"PRIuUOFF_T": "
			"type=%d", iter->filepath, offset, rec->type);
		if (unlink(iter->filepath) < 0)
			i_error("unlink(%s) failed: %m", iter->filepath);
		return NULL;
	}
	return rec;
}

int mailbox_log_iter_deinit(struct mailbox_log_iter **_iter)
{
	struct mailbox_log_iter *iter = *_iter;
	int ret = iter->failed ? -1 : 0;

	*_iter = NULL;

	if (iter->fd != -1) {
		if (close(iter->fd) < 0)
			i_error("close(%s) failed: %m", iter->filepath);
	}
	i_free(iter);
	return ret;
}