From e3b89230f065c48c84b48c88edb6eb088374c487 Mon Sep 17 00:00:00 2001 From: zautrix Date: Sat, 03 Jul 2004 16:33:12 +0000 Subject: Initial revision --- (limited to 'kmicromail/libetpan/maildir/maildir.c') diff --git a/kmicromail/libetpan/maildir/maildir.c b/kmicromail/libetpan/maildir/maildir.c new file mode 100644 index 0000000..320ef81 --- a/dev/null +++ b/kmicromail/libetpan/maildir/maildir.c @@ -0,0 +1,710 @@ +/* + * libEtPan! -- a mail stuff library + * + * Copyright (C) 2001 - 2003 - DINH Viet Hoa + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the libEtPan! project nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * $Id$ + */ + +#include "maildir.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef LIBETPAN_SYSTEM_BASENAME +#include +#endif + +/* + We suppose the maildir mailbox remains on one unique filesystem. +*/ + +struct maildir * maildir_new(const char * path) +{ + struct maildir * md; + + md = malloc(sizeof(* md)); + if (md == NULL) + goto err; + + md->mdir_counter = 0; + md->mdir_mtime_new = (time_t) -1; + md->mdir_mtime_cur = (time_t) -1; + + md->mdir_pid = getpid(); + gethostname(md->mdir_hostname, sizeof(md->mdir_hostname)); + strncpy(md->mdir_path, path, sizeof(md->mdir_path)); + md->mdir_path[PATH_MAX - 1] = '\0'; + + md->mdir_msg_list = carray_new(128); + if (md->mdir_msg_list == NULL) + goto free; + + md->mdir_msg_hash = chash_new(CHASH_DEFAULTSIZE, CHASH_COPYNONE); + if (md->mdir_msg_hash == NULL) + goto free_msg_list; + + return md; + + free_msg_list: + carray_free(md->mdir_msg_list); + free: + free(md); + err: + return NULL; +} + +static void maildir_flush(struct maildir * md, int msg_new); + +void maildir_free(struct maildir * md) +{ + maildir_flush(md, 0); + maildir_flush(md, 1); + chash_free(md->mdir_msg_hash); + carray_free(md->mdir_msg_list); + free(md); +} + +#define MAX_TRY_ALLOC 32 + +static char * maildir_get_new_message_filename(struct maildir * md, + char * tmpfile) +{ + char filename[PATH_MAX]; + char basename[PATH_MAX]; + int k; + time_t now; + + now = time(NULL); + k = 0; + while (k < MAX_TRY_ALLOC) { + snprintf(basename, sizeof(basename), "%lu.%u_%u.%s", + (unsigned long) now, md->mdir_pid, md->mdir_counter, md->mdir_hostname); + snprintf(filename, sizeof(filename), "%s/tmp/%s", + md->mdir_path, basename); + + if (link(tmpfile, filename) == 0) { + char * dup_filename; + + dup_filename = strdup(filename); + if (dup_filename == NULL) { + unlink(filename); + return NULL; + } + + unlink(tmpfile); + md->mdir_counter ++; + + return dup_filename; + } + + md->mdir_counter ++; + k ++; + } + + return NULL; +} + + +static void msg_free(struct maildir_msg * msg) +{ + free(msg->msg_uid); + free(msg->msg_filename); + free(msg); +} + +/* + msg_new() + + filename is given without path +*/ + +static struct maildir_msg * msg_new(char * filename, int new_msg) +{ + struct maildir_msg * msg; + char * p; + int flags; + size_t uid_len; + char * begin_uid; + + /* name of file : xxx-xxx_xxx-xxx:2,SRFT */ + + msg = malloc(sizeof(* msg)); + if (msg == NULL) + goto err; + + msg->msg_filename = strdup(filename); + if (msg->msg_filename == NULL) + goto free; + + begin_uid = filename; + + uid_len = strlen(begin_uid); + + flags = 0; + p = strstr(filename, ":2,"); + if (p != NULL) { + uid_len = p - begin_uid; + + p += 3; + + /* parse flags */ + while (* p != '\0') { + switch (* p) { + case 'S': + flags |= MAILDIR_FLAG_SEEN; + break; + case 'R': + flags |= MAILDIR_FLAG_REPLIED; + break; + case 'F': + flags |= MAILDIR_FLAG_FLAGGED; + break; + case 'T': + flags |= MAILDIR_FLAG_TRASHED; + break; + } + p ++; + } + } + + if (new_msg) + flags |= MAILDIR_FLAG_NEW; + + msg->msg_flags = flags; + + msg->msg_uid = malloc(uid_len + 1); + if (msg->msg_uid == NULL) + goto free_filename; + + strncpy(msg->msg_uid, begin_uid, uid_len); + msg->msg_uid[uid_len] = '\0'; + + return msg; + + free_filename: + free(msg->msg_filename); + free: + free(msg); + err: + return NULL; +} + +static void maildir_flush(struct maildir * md, int msg_new) +{ + unsigned int i; + + i = 0; + while (i < carray_count(md->mdir_msg_list)) { + struct maildir_msg * msg; + int delete; + + msg = carray_get(md->mdir_msg_list, i); + + if (msg_new) { + delete = 0; + if ((msg->msg_flags & MAILDIR_FLAG_NEW) != 0) + delete = 1; + } + else { + delete = 1; + if ((msg->msg_flags & MAILDIR_FLAG_NEW) != 0) + delete = 0; + } + + if (delete) { + chashdatum key; + + key.data = msg->msg_uid; + key.len = strlen(msg->msg_uid); + chash_delete(md->mdir_msg_hash, &key, NULL); + + carray_delete(md->mdir_msg_list, i); + msg_free(msg); + } + else { + i ++; + } + } +} + +static int add_message(struct maildir * md, + char * filename, int is_new) +{ + struct maildir_msg * msg; + chashdatum key; + chashdatum value; + unsigned int i; + int res; + int r; + + msg = msg_new(filename, is_new); + if (msg == NULL) { + res = MAILDIR_ERROR_MEMORY; + goto err; + } + + r = carray_add(md->mdir_msg_list, msg, &i); + if (r < 0) { + res = MAILDIR_ERROR_MEMORY; + goto free_msg; + } + + key.data = msg->msg_uid; + key.len = strlen(msg->msg_uid); + value.data = msg; + value.len = 0; + + r = chash_set(md->mdir_msg_hash, &key, &value, NULL); + if (r < 0) { + res = MAILDIR_ERROR_MEMORY; + goto delete; + } + + return MAILDIR_NO_ERROR; + + delete: + carray_delete(md->mdir_msg_list, i); + free_msg: + msg_free(msg); + err: + return res; +} + +static int add_directory(struct maildir * md, char * path, int is_new) +{ + DIR * d; + struct dirent * entry; + int res; + int r; + char filename[PATH_MAX]; + + d = opendir(path); + if (d == NULL) { + res = MAILDIR_ERROR_DIRECTORY; + goto err; + } + + while ((entry = readdir(d)) != NULL) { + struct stat stat_info; + + snprintf(filename, sizeof(filename), "%s/%s", path, entry->d_name); + r = stat(filename, &stat_info); + if (r < 0) + continue; + + if (S_ISDIR(stat_info.st_mode)) + continue; + + r = add_message(md, entry->d_name, is_new); + if (r != MAILDIR_NO_ERROR) { + /* ignore errors */ + } + } + + closedir(d); + + return MAILDIR_NO_ERROR; + + err: + return res; +} + +int maildir_update(struct maildir * md) +{ + struct stat stat_info; + char path_new[PATH_MAX]; + char path_cur[PATH_MAX]; + int r; + int res; + + snprintf(path_new, sizeof(path_new), "%s/new", md->mdir_path); + snprintf(path_cur, sizeof(path_cur), "%s/cur", md->mdir_path); + + /* did new/ changed ? */ + + r = stat(path_new, &stat_info); + if (r < 0) { + res = MAILDIR_ERROR_DIRECTORY; + goto free; + } + + if (md->mdir_mtime_new != stat_info.st_mtime) { + md->mdir_mtime_new = stat_info.st_mtime; + + maildir_flush(md, 1); + + /* messages in new */ + r = add_directory(md, path_new, 1); + if (r != MAILDIR_NO_ERROR) { + res = r; + goto free; + } + } + + /* did cur/ changed ? */ + + r = stat(path_cur, &stat_info); + if (r < 0) { + res = MAILDIR_ERROR_DIRECTORY; + goto free; + } + + if (md->mdir_mtime_cur != stat_info.st_mtime) { + md->mdir_mtime_cur = stat_info.st_mtime; + + maildir_flush(md, 0); + + /* messages in cur */ + r = add_directory(md, path_cur, 0); + if (r != MAILDIR_NO_ERROR) { + res = r; + goto free; + } + } + + return MAILDIR_NO_ERROR; + + free: + maildir_flush(md, 0); + maildir_flush(md, 1); + md->mdir_mtime_cur = (time_t) -1; + md->mdir_mtime_new = (time_t) -1; + return res; +} + +#ifndef LIBETPAN_SYSTEM_BASENAME +static char * libetpan_basename(char * filename) +{ + char * next; + char * p; + + p = filename; + next = strchr(p, '/'); + + while (next != NULL) { + p = next; + next = strchr(p + 1, '/'); + } + + if (p == filename) + return filename; + else + return p + 1; +} +#else +#define libetpan_basename(a) basename(a) +#endif + +int maildir_message_add(struct maildir * md, + const char * message, size_t size) +{ + char path_new[PATH_MAX]; + char tmpname[PATH_MAX]; + int fd; + int r; + char * mapping; + char * delivery_tmp_name; + char * delivery_tmp_basename; + char delivery_new_name[PATH_MAX]; + char * delivery_new_basename; + int res; + struct stat stat_info; + + r = maildir_update(md); + if (r != MAILDIR_NO_ERROR) { + res = r; + goto err; + } + + /* write to tmp/ with a classic temporary file */ + + snprintf(tmpname, sizeof(tmpname), "%s/tmp/etpan-maildir-XXXXXX", md->mdir_path); + fd = mkstemp(tmpname); + if (fd < 0) { + res = MAILDIR_ERROR_FILE; + goto err; + } + + r = ftruncate(fd, size); + if (r < 0) { + res = MAILDIR_ERROR_FILE; + goto close; + } + + mapping = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (mapping == MAP_FAILED) { + res = MAILDIR_ERROR_FILE; + goto close; + } + + memcpy(mapping, message, size); + + msync(mapping, size, MS_SYNC); + munmap(mapping, size); + + close(fd); + + /* write to tmp/ with maildir standard name */ + + delivery_tmp_name = maildir_get_new_message_filename(md, tmpname); + if (delivery_tmp_name == NULL) { + res = MAILDIR_ERROR_FILE; + goto unlink; + } + + /* write to new/ with maildir standard name */ + + strncpy(tmpname, delivery_tmp_name, sizeof(tmpname)); + tmpname[sizeof(tmpname) - 1] = '\0'; + + delivery_tmp_basename = libetpan_basename(tmpname); + + snprintf(delivery_new_name, sizeof(delivery_new_name), "%s/new/%s", + md->mdir_path, delivery_tmp_basename); + + r = link(delivery_tmp_name, delivery_new_name); + if (r < 0) { + res = MAILDIR_ERROR_FILE; + goto unlink_tmp; + } + + snprintf(path_new, sizeof(path_new), "%s/new", md->mdir_path); + r = stat(path_new, &stat_info); + if (r < 0) { + unlink(delivery_new_name); + res = MAILDIR_ERROR_FILE; + goto unlink_tmp; + } + + md->mdir_mtime_new = stat_info.st_mtime; + + delivery_new_basename = libetpan_basename(delivery_new_name); + + r = add_message(md, delivery_new_basename, 1); + if (r != MAILDIR_NO_ERROR) { + unlink(delivery_new_name); + res = MAILDIR_ERROR_FILE; + goto unlink_tmp; + } + + unlink(delivery_tmp_name); + free(delivery_tmp_name); + + return MAILDIR_NO_ERROR; + + unlink_tmp: + unlink(delivery_tmp_name); + free(delivery_tmp_name); + goto err; + close: + close(fd); + unlink: + unlink(tmpname); + err: + return res; +} + +int maildir_message_add_file(struct maildir * md, int fd) +{ + char * message; + struct stat buf; + int r; + + if (fstat(fd, &buf) == -1) + return MAILDIR_ERROR_FILE; + + message = mmap(NULL, buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (message == MAP_FAILED) + return MAILDIR_ERROR_FILE; + + r = maildir_message_add(md, message, buf.st_size); + + munmap(message, buf.st_size); + + return r; +} + +char * maildir_message_get(struct maildir * md, const char * uid) +{ + chashdatum key; + chashdatum value; + char filename[PATH_MAX]; + char * dup_filename; + struct maildir_msg * msg; + char * dir; + int r; + + key.data = (void *) uid; + key.len = strlen(uid); + r = chash_get(md->mdir_msg_hash, &key, &value); + if (r < 0) + return NULL; + + msg = value.data; + if ((msg->msg_flags & MAILDIR_FLAG_NEW) != 0) + dir = "new"; + else + dir = "cur"; + + snprintf(filename, sizeof(filename), "%s/%s/%s", + md->mdir_path, dir, msg->msg_filename); + + dup_filename = strdup(filename); + if (dup_filename == NULL) + return NULL; + + return dup_filename; +} + +int maildir_message_remove(struct maildir * md, const char * uid) +{ + chashdatum key; + chashdatum value; + char filename[PATH_MAX]; + struct maildir_msg * msg; + char * dir; + int r; + int res; + + key.data = (void *) uid; + key.len = strlen(uid); + r = chash_get(md->mdir_msg_hash, &key, &value); + if (r < 0) { + res = MAILDIR_ERROR_NOT_FOUND; + goto err; + } + + msg = value.data; + if ((msg->msg_flags & MAILDIR_FLAG_NEW) != 0) + dir = "new"; + else + dir = "cur"; + + snprintf(filename, sizeof(filename), "%s/%s/%s", + md->mdir_path, dir, msg->msg_filename); + + r = unlink(filename); + if (r < 0) { + res = MAILDIR_ERROR_FILE; + goto err; + } + + return MAILDIR_NO_ERROR; + + err: + return res; +} + +int maildir_message_change_flags(struct maildir * md, + const char * uid, int new_flags) +{ + chashdatum key; + chashdatum value; + char filename[PATH_MAX]; + struct maildir_msg * msg; + char * dir; + int r; + char new_filename[PATH_MAX]; + char flag_str[5]; + size_t i; + int res; + + key.data = (void *) uid; + key.len = strlen(uid); + r = chash_get(md->mdir_msg_hash, &key, &value); + if (r < 0) { + res = MAILDIR_ERROR_NOT_FOUND; + goto err; + } + + msg = value.data; + if ((msg->msg_flags & MAILDIR_FLAG_NEW) != 0) + dir = "new"; + else + dir = "cur"; + + snprintf(filename, sizeof(filename), "%s/%s/%s", + md->mdir_path, dir, msg->msg_filename); + + if ((new_flags & MAILDIR_FLAG_NEW) != 0) + dir = "new"; + else + dir = "cur"; + + i = 0; + if ((new_flags & MAILDIR_FLAG_SEEN) != 0) { + flag_str[i] = 'S'; + i ++; + } + if ((new_flags & MAILDIR_FLAG_REPLIED) != 0) { + flag_str[i] = 'R'; + i ++; + } + if ((new_flags & MAILDIR_FLAG_FLAGGED) != 0) { + flag_str[i] = 'F'; + i ++; + } + if ((new_flags & MAILDIR_FLAG_TRASHED) != 0) { + flag_str[i] = 'T'; + i ++; + } + flag_str[i] = 0; + + if (flag_str[0] == '\0') + snprintf(new_filename, sizeof(new_filename), "%s/%s/%s", + md->mdir_path, dir, msg->msg_uid); + else + snprintf(new_filename, sizeof(new_filename), "%s/%s/%s:2,%s", + md->mdir_path, dir, msg->msg_uid, flag_str); + + if (strcmp(filename, new_filename) == 0) + return MAILDIR_NO_ERROR; + + r = link(filename, new_filename); + if (r < 0) { + res = MAILDIR_ERROR_FILE; + goto err; + } + + unlink(filename); + + return MAILDIR_NO_ERROR; + + err: + return res; +} -- cgit v0.9.0.2