1
/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */
7
#include "home-expand.h"
8
#include "file-dotlock.h"
10
#include "duplicate.h"
16
#define DUPLICATE_PATH "~/.dovecot.lda-dupes"
17
#define COMPRESS_PERCENTAGE 10
18
#define DUPLICATE_BUFSIZE 4096
19
#define DUPLICATE_VERSION 2
29
struct duplicate_file_header {
33
struct duplicate_record_header {
39
struct duplicate_file {
41
struct hash_table *hash;
45
struct dotlock *dotlock;
46
unsigned int changed:1;
49
static struct dotlock_settings duplicate_dotlock_set = {
50
MEMBER(temp_prefix) NULL,
51
MEMBER(lock_suffix) NULL,
54
MEMBER(stale_timeout) 10,
56
MEMBER(callback) NULL,
59
MEMBER(use_excl_lock) FALSE
61
static struct duplicate_file *duplicate_file = NULL;
63
static int duplicate_cmp(const void *p1, const void *p2)
65
const struct duplicate *d1 = p1, *d2 = p2;
67
return (d1->id_size == d2->id_size &&
68
memcmp(d1->id, d2->id, d1->id_size) == 0 &&
69
strcasecmp(d1->user, d2->user) == 0) ? 0 : 1;
72
static unsigned int duplicate_hash(const void *p)
74
/* a char* hash function from ASU -- from glib */
75
const struct duplicate *d = p;
76
const unsigned char *s = d->id, *end = s + d->id_size;
77
unsigned int g, h = 0;
81
if ((g = h & 0xf0000000UL)) {
88
return h ^ strcase_hash(d->user);
92
duplicate_read_records(struct duplicate_file *file, struct istream *input,
93
unsigned int record_size)
95
const unsigned char *data;
96
struct duplicate_record_header hdr;
98
unsigned int change_count;
101
while (i_stream_read_data(input, &data, &size, record_size) > 0) {
102
if (record_size == sizeof(hdr))
103
memcpy(&hdr, data, sizeof(hdr));
105
/* FIXME: backwards compatibility with v1.0 */
108
i_assert(record_size ==
109
sizeof(time_t) + sizeof(uint32_t)*2);
110
memcpy(&stamp, data, sizeof(stamp));
112
memcpy(&hdr.id_size, data + sizeof(time_t),
113
sizeof(hdr.id_size));
114
memcpy(&hdr.user_size,
115
data + sizeof(time_t) + sizeof(uint32_t),
116
sizeof(hdr.user_size));
118
i_stream_skip(input, record_size);
120
if (hdr.id_size == 0 || hdr.user_size == 0 ||
121
hdr.id_size > DUPLICATE_BUFSIZE ||
122
hdr.user_size > DUPLICATE_BUFSIZE) {
123
i_error("broken duplicate file %s", file->path);
127
if (i_stream_read_data(input, &data, &size,
128
hdr.id_size + hdr.user_size - 1) <= 0) {
129
i_error("unexpected end of file in %s", file->path);
133
if ((time_t)hdr.stamp >= ioloop_time) {
134
/* still valid, save it */
138
new_id = p_malloc(file->pool, hdr.id_size);
139
memcpy(new_id, data, hdr.id_size);
141
d = p_new(file->pool, struct duplicate, 1);
143
d->id_size = hdr.id_size;
144
d->user = p_strndup(file->pool,
145
data + hdr.id_size, hdr.user_size);
147
hash_table_insert(file->hash, d, d);
151
i_stream_skip(input, hdr.id_size + hdr.user_size);
154
if (hash_table_count(file->hash) *
155
COMPRESS_PERCENTAGE / 100 > change_count)
156
file->changed = TRUE;
160
static int duplicate_read(struct duplicate_file *file)
162
struct istream *input;
163
struct duplicate_file_header hdr;
164
const unsigned char *data;
167
unsigned int record_size = 0;
169
fd = open(file->path, O_RDONLY);
173
i_error("open(%s) failed: %m", file->path);
177
/* <timestamp> <id_size> <user_size> <id> <user> */
178
input = i_stream_create_fd(fd, DUPLICATE_BUFSIZE, FALSE);
179
if (i_stream_read_data(input, &data, &size, sizeof(hdr)) > 0) {
180
memcpy(&hdr, data, sizeof(hdr));
181
if (hdr.version == 0 || hdr.version > DUPLICATE_VERSION + 10) {
182
/* FIXME: backwards compatibility with v1.0 */
183
record_size = sizeof(time_t) + sizeof(uint32_t)*2;
184
} else if (hdr.version == DUPLICATE_VERSION) {
185
record_size = sizeof(struct duplicate_record_header);
186
i_stream_skip(input, sizeof(hdr));
190
if (record_size == 0 ||
191
duplicate_read_records(file, input, record_size) < 0) {
192
if (unlink(file->path) < 0 && errno != ENOENT)
193
i_error("unlink(%s) failed: %m", file->path);
196
i_stream_unref(&input);
198
i_error("close(%s) failed: %m", file->path);
202
static struct duplicate_file *duplicate_new(const char *path)
204
struct duplicate_file *file;
207
pool = pool_alloconly_create("duplicates", 10240);
209
file = p_new(pool, struct duplicate_file, 1);
211
file->path = p_strdup(pool, path);
212
file->new_fd = file_dotlock_open(&duplicate_dotlock_set, path, 0,
214
if (file->new_fd == -1)
215
i_error("file_dotlock_create(%s) failed: %m", path);
216
file->hash = hash_table_create(default_pool, pool, 0,
217
duplicate_hash, duplicate_cmp);
218
(void)duplicate_read(file);
222
static void duplicate_free(struct duplicate_file **_file)
224
struct duplicate_file *file = *_file;
227
if (file->dotlock != NULL)
228
file_dotlock_delete(&file->dotlock);
230
hash_table_destroy(&file->hash);
231
pool_unref(&file->pool);
234
int duplicate_check(const void *id, size_t id_size, const char *user)
238
if (duplicate_file == NULL)
239
duplicate_file = duplicate_new(home_expand(DUPLICATE_PATH));
245
return hash_table_lookup(duplicate_file->hash, &d) != NULL;
248
void duplicate_mark(const void *id, size_t id_size,
249
const char *user, time_t timestamp)
254
if (duplicate_file == NULL)
255
duplicate_file = duplicate_new(home_expand(DUPLICATE_PATH));
257
new_id = p_malloc(duplicate_file->pool, id_size);
258
memcpy(new_id, id, id_size);
260
d = p_new(duplicate_file->pool, struct duplicate, 1);
262
d->id_size = id_size;
263
d->user = p_strdup(duplicate_file->pool, user);
266
duplicate_file->changed = TRUE;
267
hash_table_insert(duplicate_file->hash, d, d);
270
void duplicate_flush(void)
272
struct duplicate_file *file = duplicate_file;
273
struct duplicate_file_header hdr;
274
struct duplicate_record_header rec;
275
struct ostream *output;
276
struct hash_iterate_context *iter;
279
if (duplicate_file == NULL || !file->changed || file->new_fd == -1)
282
memset(&hdr, 0, sizeof(hdr));
283
hdr.version = DUPLICATE_VERSION;
285
output = o_stream_create_fd_file(file->new_fd, 0, FALSE);
286
o_stream_send(output, &hdr, sizeof(hdr));
288
memset(&rec, 0, sizeof(rec));
289
iter = hash_table_iterate_init(file->hash);
290
while (hash_table_iterate(iter, &key, &value)) {
291
struct duplicate *d = value;
294
rec.id_size = d->id_size;
295
rec.user_size = strlen(d->user);
297
o_stream_send(output, &rec, sizeof(rec));
298
o_stream_send(output, d->id, rec.id_size);
299
o_stream_send(output, d->user, rec.user_size);
301
hash_table_iterate_deinit(&iter);
302
o_stream_unref(&output);
304
file->changed = FALSE;
305
if (file_dotlock_replace(&file->dotlock, 0) < 0)
306
i_error("file_dotlock_replace(%s) failed: %m", file->path);
310
void duplicate_init(void)
312
duplicate_dotlock_set.use_excl_lock =
313
getenv("DOTLOCK_USE_EXCL") != NULL;
314
duplicate_dotlock_set.nfs_flush =
315
getenv("MAIL_NFS_STORAGE") != NULL;
318
void duplicate_deinit(void)
320
if (duplicate_file != NULL) {
322
duplicate_free(&duplicate_file);