view src/lib-storage/index/maildir/maildir-util.c @ 1947:777da553d1d3 HEAD

Recent-flag should work now
author Timo Sirainen <tss@iki.fi>
date Sat, 01 May 2004 21:30:52 +0300
parents 79790750c349
children 4dec6a3d79fd
line wrap: on
line source

/* Copyright (C) 2004 Timo Sirainen */

#include "lib.h"
#include "hostpid.h"
#include "ioloop.h"
#include "str.h"
#include "maildir-storage.h"
#include "maildir-uidlist.h"

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

static int maildir_file_do_try(struct index_mailbox *ibox, uint32_t uid,
			       maildir_file_do_func *func, void *context)
{
	const char *fname, *path;
        enum maildir_uidlist_rec_flag flags;
	int ret;

	fname = maildir_uidlist_lookup(ibox->uidlist, uid, &flags);
	if (fname == NULL)
		return -2; /* expunged */

	if ((flags & MAILDIR_UIDLIST_REC_FLAG_NEW_DIR) != 0) {
		/* probably in new/ dir */
		path = t_strconcat(ibox->path, "/new/", fname, NULL);
		ret = func(ibox, path, context);
		if (ret != 0)
			return ret;
	}

	path = t_strconcat(ibox->path, "/cur/", fname, NULL);
	return func(ibox, path, context);
}

int maildir_file_do(struct index_mailbox *ibox, uint32_t uid,
		    maildir_file_do_func *func, void *context)
{
	int i, ret;

	ret = maildir_file_do_try(ibox, uid, func, context);
	for (i = 0; i < 10 && ret == 0; i++) {
		/* file is either renamed or deleted. sync the maildir and
		   see which one. if file appears to be renamed constantly,
		   don't try to open it more than 10 times. */
		if (maildir_storage_sync_readonly(ibox) < 0)
			return -1;

		ret = maildir_file_do_try(ibox, uid, func, context);
	}

	return ret == -2 ? 0 : ret;
}

int maildir_filename_get_flags(const char *fname, enum mail_flags *flags_r,
			       custom_flags_mask_t custom_flags_r)
{
	const char *info;
	unsigned int num;

	*flags_r = 0;
	memset(custom_flags_r, 0, INDEX_CUSTOM_FLAGS_BYTE_COUNT);

	info = strchr(fname, ':');
	if (info == NULL || info[1] != '2' || info[2] != ',')
		return 0;

	for (info += 3; *info != '\0' && *info != ','; info++) {
		switch (*info) {
		case 'R': /* replied */
			*flags_r |= MAIL_ANSWERED;
			break;
		case 'S': /* seen */
			*flags_r |= MAIL_SEEN;
			break;
		case 'T': /* trashed */
			*flags_r |= MAIL_DELETED;
			break;
		case 'D': /* draft */
			*flags_r |= MAIL_DRAFT;
			break;
		case 'F': /* flagged */
			*flags_r |= MAIL_FLAGGED;
			break;
		default:
			if (*info >= 'a' && *info <= 'z') {
				/* custom flag */
				num = (*info - 'a');
				custom_flags_r[num / CHAR_BIT] |=
					num % CHAR_BIT;
				break;
			}

			/* unknown flag - ignore */
			break;
		}
	}

	return 1;
}

const char *maildir_filename_set_flags(const char *fname, enum mail_flags flags,
				       custom_flags_mask_t custom_flags)
{
	string_t *flags_str;
	const char *info, *oldflags;
	int i, nextflag;

	if (custom_flags != NULL) {
		/* see if any custom flags are given */
		for (i = 0; i < INDEX_CUSTOM_FLAGS_BYTE_COUNT; i++) {
			if (custom_flags[i] != 0)
				break;
		}
		if (i == INDEX_CUSTOM_FLAGS_BYTE_COUNT)
			custom_flags = NULL;
	}

	/* remove the old :info from file name, and get the old flags */
	info = strrchr(fname, ':');
	if (info != NULL && strrchr(fname, '/') > info)
		info = NULL;

	oldflags = "";
	if (info != NULL) {
		fname = t_strdup_until(fname, info);
		if (info[1] == '2' && info[2] == ',')
			oldflags = info+3;
	}

	/* insert the new flags between old flags. flags must be sorted by
	   their ASCII code. unknown flags are kept. */
	flags_str = t_str_new(256);
	str_append(flags_str, fname);
	str_append(flags_str, ":2,");
	for (;;) {
		/* skip all known flags */
		while (*oldflags == 'D' || *oldflags == 'F' ||
		       *oldflags == 'R' || *oldflags == 'S' ||
		       *oldflags == 'T' ||
		       (*oldflags >= 'a' && *oldflags <= 'z'))
			oldflags++;

		nextflag = *oldflags == '\0' || *oldflags == ',' ? 256 :
			(unsigned char) *oldflags;

		if ((flags & MAIL_DRAFT) && nextflag > 'D') {
			str_append_c(flags_str, 'D');
			flags &= ~MAIL_DRAFT;
		}
		if ((flags & MAIL_FLAGGED) && nextflag > 'F') {
			str_append_c(flags_str, 'F');
			flags &= ~MAIL_FLAGGED;
		}
		if ((flags & MAIL_ANSWERED) && nextflag > 'R') {
			str_append_c(flags_str, 'R');
			flags &= ~MAIL_ANSWERED;
		}
		if ((flags & MAIL_SEEN) && nextflag > 'S') {
			str_append_c(flags_str, 'S');
			flags &= ~MAIL_SEEN;
		}
		if ((flags & MAIL_DELETED) && nextflag > 'T') {
			str_append_c(flags_str, 'T');
			flags &= ~MAIL_DELETED;
		}

		if (custom_flags != NULL && nextflag > 'a') {
			for (i = 0; i < INDEX_CUSTOM_FLAGS_COUNT; i++) {
				if ((custom_flags[i / CHAR_BIT] &
				     (1 << (i % CHAR_BIT))) != 0)
					str_append_c(flags_str, 'a' + i);
			}
			custom_flags = NULL;
		}

		if (*oldflags == '\0' || *oldflags == ',')
			break;

		str_append_c(flags_str, *oldflags);
		oldflags++;
	}

	if (*oldflags == ',') {
		/* another flagset, we don't know about these, just keep them */
		while (*oldflags != '\0')
			str_append_c(flags_str, *oldflags++);
	}

	return str_c(flags_str);
}

const char *maildir_generate_tmp_filename(const struct timeval *tv)
{
	static unsigned int create_count = 0;
	static time_t first_stamp = 0;

	if (first_stamp == 0 || first_stamp == ioloop_time) {
		/* it's possible that within last second another process had
		   the same UID as us. Use usecs to make sure we don't create
		   duplicate base name. */
		first_stamp = ioloop_time;
		return t_strdup_printf("%s.P%sQ%uM%s.%s",
				       dec2str(tv->tv_sec), my_pid,
				       create_count++,
				       dec2str(tv->tv_usec), my_hostname);
	} else {
		/* Don't bother with usecs. Saves a bit space :) */
		return t_strdup_printf("%s.P%sQ%u.%s",
				       dec2str(tv->tv_sec), my_pid,
				       create_count++, my_hostname);
	}
}

int maildir_create_tmp(struct index_mailbox *ibox, const char *dir,
		       mode_t mode, const char **fname_r)
{
	const char *path, *tmp_fname;
	struct stat st;
	struct timeval *tv, tv_now;
	pool_t pool;
	int fd;

	tv = &ioloop_timeval;
	pool = pool_alloconly_create("maildir_tmp", 4096);
	for (;;) {
		p_clear(pool);
		tmp_fname = maildir_generate_tmp_filename(tv);

		path = p_strconcat(pool, dir, "/", tmp_fname, NULL);
		if (stat(path, &st) < 0 && errno == ENOENT) {
			/* doesn't exist */
			mode_t old_mask = umask(0);
			fd = open(path, O_WRONLY | O_CREAT | O_EXCL, mode);
			umask(old_mask);
			if (fd != -1 || errno != EEXIST)
				break;
		}

		/* wait and try again - very unlikely */
		sleep(2);
		tv = &tv_now;
		if (gettimeofday(&tv_now, NULL) < 0)
			i_fatal("gettimeofday(): %m");
	}

	*fname_r = t_strdup(path);
	if (fd == -1) {
		mail_storage_set_critical(ibox->box.storage,
					  "open(%s) failed: %m", path);
	}

	pool_unref(pool);
	return fd;
}

/* a char* hash function from ASU -- from glib */
unsigned int maildir_hash(const void *p)
{
        const unsigned char *s = p;
	unsigned int g, h = 0;

	while (*s != ':' && *s != '\0') {
		h = (h << 4) + *s;
		if ((g = h & 0xf0000000UL)) {
			h = h ^ (g >> 24);
			h = h ^ g;
		}

		s++;
	}

	return h;
}

int maildir_cmp(const void *p1, const void *p2)
{
	const char *s1 = p1, *s2 = p2;

	while (*s1 == *s2 && *s1 != ':' && *s1 != '\0') {
		s1++; s2++;
	}
	if ((*s1 == '\0' || *s1 == ':') &&
	    (*s2 == '\0' || *s2 == ':'))
		return 0;
	return *s1 - *s2;
}