summaryrefslogtreecommitdiffstats
path: root/src/lib-index/mailbox-log.c
blob: 433ba298a44255905496d7a8785f5cebb1e4b792 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
/* Copyright (c) 2009-2018 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>

/* How often to reopen the log file to make sure that the changes are written
   to the latest file. The main problem here is if the value is too high the
   changes could be written to a file that was already rotated and deleted.
   That wouldn't happen in any real world situations though, since the file
   rotation time is probably measured in months or years. Still, each session
   rarely writes anything here, so the value can just as well be a pretty small
   one without any performance problems. */
#define MAILBOX_LOG_REOPEN_SECS (60)
#define MAILBOX_LOG_ROTATE_SIZE (1024*4)

struct mailbox_log {
	char *filepath, *filepath2;
	int fd;
	struct event *event;
	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(struct event *parent_event, const char *path)
{
	struct mailbox_log *log;

	log = i_new(struct mailbox_log, 1);
	log->event = event_create(parent_event);
	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);
	event_unref(&log->event);
	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)
{
	i_close_fd_path(&log->fd, log->filepath);
}

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)
			e_error(log->event, "creat(%s) failed: %m",
				log->filepath);
		else
			e_error(log->event, "%s",
				eacces_error_get("creat", log->filepath));
		return -1;
	}
	if (fchown(log->fd, (uid_t)-1, log->gid) < 0) {
		if (errno != EPERM)
			e_error(log->event, "fchown(%s) failed: %m",
				log->filepath);
		else {
			e_error(log->event, "%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) {
		e_error(log->event, "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) {
		e_error(log->event, "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)
{
	cpu32_to_be_unaligned(stamp, rec->timestamp);
}

time_t mailbox_log_record_get_timestamp(const struct mailbox_log_record *rec)
{
	return (time_t) be32_to_cpu_unaligned(rec->timestamp);
}

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/MAILBOX_LOG_REOPEN_SECS !=
	    ioloop_time/MAILBOX_LOG_REOPEN_SECS)
		mailbox_log_close(log);
	if (log->fd == -1) {
		if (mailbox_log_open(log) < 0)
			return -1;
		i_assert(log->fd != -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) {
		e_error(log->event, "write(%s) failed: %m", log->filepath);
		return -1;
	} else if (ret != sizeof(*rec)) {
		e_error(log->event, "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) {
				e_error(log->event, "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)
{
	i_close_fd_path(&iter->fd, iter->filepath);
	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 {
		e_error(iter->log->event, "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) {
			e_error(iter->log->event, "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]);
		e_error(iter->log->event,
			"Corrupted mailbox log %s at offset %"PRIuUOFF_T": "
			"type=%d", iter->filepath, offset, rec->type);
		i_unlink(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;

	i_close_fd_path(&iter->fd, iter->filepath);
	i_free(iter);
	return ret;
}