static bool
mail_thread_unref_link(struct mail_thread_cache *cache,
		       uint32_t parent_idx, uint32_t child_idx)
{
	struct mail_thread_node *parent, *child;

	parent = array_idx_modifiable(&cache->thread_nodes, parent_idx);
	if (parent->child_unref_rebuilds)
		return FALSE;

	child = array_idx_modifiable(&cache->thread_nodes, child_idx);
	i_assert(child->parent_link_refcount > 0);

	child->parent_link_refcount--;
	if (child->parent_link_refcount == 0) {
		/* we don't have a root anymore */
		child->parent_idx = 0;
	}
	return TRUE;
}
Example #2
0
void mail_index_register_sync_handler(struct mail_index *index, uint32_t ext_id,
				      mail_index_sync_handler_t *cb,
				      enum mail_index_sync_handler_type type)
{
	struct mail_index_registered_ext *rext;

	rext = array_idx_modifiable(&index->extensions, ext_id);
	i_assert(rext->sync_handler.callback == NULL);

	rext->sync_handler.callback = cb;
	rext->sync_handler.type = type;
}
static uint32_t thread_msg_add(struct mail_thread_cache *cache,
			       uint32_t uid, uint32_t msgid_idx)
{
	struct mail_thread_node *node;

	i_assert(msgid_idx != 0);
	i_assert(msgid_idx < cache->first_invalid_msgid_str_idx);

	node = array_idx_modifiable(&cache->thread_nodes, msgid_idx);
	if (node->uid == 0)
		node->uid = uid;
	else {
		/* duplicate message-id, keep the original.
		   if the original ever gets expunged, rebuild. */
		node->expunge_rebuilds = TRUE;

		msgid_idx = cache->next_invalid_msgid_str_idx++;
		node = array_idx_modifiable(&cache->thread_nodes, msgid_idx);
		node->uid = uid;
	}
	return msgid_idx;
}
Example #4
0
void mail_index_ext_register_resize_defaults(struct mail_index *index,
					     uint32_t ext_id,
					     uint32_t default_hdr_size,
					     uint16_t default_record_size,
					     uint16_t default_record_align)
{
	struct mail_index_registered_ext *rext;

	rext = array_idx_modifiable(&index->extensions, ext_id);
	rext->hdr_size = default_hdr_size;
	rext->record_size = default_record_size;
	rext->record_align = default_record_align;
}
Example #5
0
void mail_index_register_expunge_handler(struct mail_index *index,
					 uint32_t ext_id, bool call_always,
					 mail_index_expunge_handler_t *cb,
					 void *context)
{
	struct mail_index_registered_ext *rext;

	rext = array_idx_modifiable(&index->extensions, ext_id);
	i_assert(rext->expunge_handler == NULL || rext->expunge_handler == cb);

	rext->expunge_handler = cb;
	rext->expunge_context = context;
	rext->expunge_handler_call_always = call_always;
}
Example #6
0
struct smtp_server_reply *
smtp_server_command_get_reply(struct smtp_server_command *cmd,
	unsigned int idx)
{
	struct smtp_server_reply *reply;

	if (!array_is_created(&cmd->replies))
		return NULL;

	reply = array_idx_modifiable(&cmd->replies, idx);
	if (!reply->submitted)
		return NULL;
	return reply;
}
static void doveadm_print_formatted_print(const char *value)
{
	if (ctx.format == NULL) {
		i_fatal("formatted formatter cannot be used without a format.");
	}
	struct var_expand_table *entry = array_idx_modifiable(&ctx.headers, ctx.idx++);
	entry->value = value;

	if (ctx.idx >= array_count(&ctx.headers)) {
		var_expand(ctx.buf, ctx.format, array_idx(&ctx.headers,0));
		doveadm_print_formatted_flush();
		ctx.idx = 0;
	}

}
bool mail_thread_remove(struct mail_thread_cache *cache,
			const struct mail_index_strmap_rec *msgid_map,
			unsigned int *msgid_map_idx)
{
	struct mail_thread_node *node;
	uint32_t idx, parent_idx;
	unsigned int count = 1;

	idx = msgid_map->str_idx;
	i_assert(idx != 0);

	if (msgid_map->uid > cache->last_uid) {
		/* this message was never added to the cache, skip */
		while (msgid_map[count].uid == msgid_map->uid)
			count++;
		*msgid_map_idx += count;
		return TRUE;
	}

	node = array_idx_modifiable(&cache->thread_nodes, idx);
	if (node->expunge_rebuilds) {
		/* this catches the duplicate message-id case */
		return FALSE;
	}
	i_assert(node->uid == msgid_map->uid);

	/* update link refcounts */
	if (msgid_map[count].uid == node->uid) {
		parent_idx = msgid_map[count].str_idx;
		count++;
		while (msgid_map[count].uid == node->uid) {
			if (!mail_thread_unref_link(cache, parent_idx,
						    msgid_map[count].str_idx))
				return FALSE;
			parent_idx = msgid_map[count].str_idx;
			count++;
		}
		if (!mail_thread_unref_link(cache, parent_idx, idx))
			return FALSE;
	}
	/* mark this message as expunged */
	node->uid = 0;

	/* we don't know (and don't want to waste time figuring out) if other
	   messages point to this removed message, so don't delete the node */
	*msgid_map_idx += count;
	return TRUE;
}
Example #9
0
static void
maildir_keywords_create(struct maildir_keywords *mk, const char *name,
			unsigned int chridx)
{
	const char **strp;
	char *new_name;

	i_assert(chridx < MAILDIR_MAX_KEYWORDS);

	new_name = p_strdup(mk->pool, name);
	hash_table_insert(mk->hash, new_name, POINTER_CAST(chridx + 1));

	strp = array_idx_modifiable(&mk->list, chridx);
	*strp = new_name;

	mk->changed = TRUE;
}
Example #10
0
static void
mail_cache_merge_bitmask(struct mail_cache_copy_context *ctx,
			 const struct mail_cache_iterate_field *field)
{
	unsigned char *dest;
	unsigned int i, *pos;

	pos = array_idx_modifiable(&ctx->bitmask_pos, field->field_idx);
	if (*pos == 0) {
		/* we decided to drop this field */
		return;
	}

	dest = buffer_get_space_unsafe(ctx->buffer, *pos, field->size);
	for (i = 0; i < field->size; i++)
		dest[i] |= ((const unsigned char*)field->data)[i];
}
Example #11
0
static void
lmtp_client_rcpt_next(struct lmtp_client *client, const char *line)
{
	struct lmtp_rcpt *rcpt;
	bool success;

	success = line[0] == '2';
	if (success)
		client->rcpt_to_successes = TRUE;

	rcpt = array_idx_modifiable(&client->recipients,
				    client->rcpt_next_receive_idx);
	client->rcpt_next_receive_idx++;

	rcpt->failed = !success;
	rcpt->rcpt_to_callback(success, line, rcpt->context);
}
Example #12
0
static int pop3_map_read_hdr_hashes(struct mail_storage *storage,
				    unsigned first_seq)
{
	struct pop3_migration_mail_storage *mstorage =
		POP3_MIGRATION_CONTEXT(storage);
        struct mailbox_transaction_context *t;
	struct mail_search_args *search_args;
	struct mail_search_context *ctx;
	struct mail *mail;
	struct pop3_uidl_map *map;
	int ret = 0;

	if (mstorage->pop3_all_hdr_sha1_set)
		return 0;
	if (mstorage->all_mailboxes) {
		/* we may be matching against multiple mailboxes.
		   read all the hashes only once. */
		first_seq = 1;
	}

	t = mailbox_transaction_begin(mstorage->pop3_box, 0);
	search_args = mail_search_build_init();
	mail_search_build_add_seqset(search_args, first_seq,
				     array_count(&mstorage->pop3_uidl_map)+1);
	ctx = mailbox_search_init(t, search_args, NULL,
				  MAIL_FETCH_STREAM_HEADER, NULL);
	mail_search_args_unref(&search_args);

	while (mailbox_search_next(ctx, &mail)) {
		map = array_idx_modifiable(&mstorage->pop3_uidl_map,
					   mail->seq-1);

		if (get_hdr_sha1(mail, map->hdr_sha1) < 0)
			ret = -1;
		else
			map->hdr_sha1_set = TRUE;
	}

	if (mailbox_search_deinit(&ctx) < 0)
		ret = -1;
	(void)mailbox_transaction_commit(&t);
	if (ret == 0 && first_seq == 1)
		mstorage->pop3_all_hdr_sha1_set = TRUE;
	return ret;
}
Example #13
0
static void
index_storage_mailbox_update_cache(struct mailbox *box,
				   const struct mailbox_update *update)
{
	const struct mailbox_cache_field *updates = update->cache_updates;
	ARRAY(struct mail_cache_field) new_fields;
	const struct mail_cache_field *old_fields;
	struct mail_cache_field field;
	unsigned int i, j, old_count;

	old_fields = mail_cache_register_get_list(box->cache,
						  pool_datastack_create(),
						  &old_count);

	/* There shouldn't be many fields, so don't worry about O(n^2). */
	t_array_init(&new_fields, 32);
	for (i = 0; updates[i].name != NULL; i++) {
		/* see if it's an existing field */
		for (j = 0; j < old_count; j++) {
			if (strcmp(updates[i].name, old_fields[j].name) == 0)
				break;
		}
		if (j != old_count) {
			field = old_fields[j];
		} else if (strncmp(updates[i].name, "hdr.", 4) == 0) {
			/* new header */
			memset(&field, 0, sizeof(field));
			field.name = updates[i].name;
			field.type = MAIL_CACHE_FIELD_HEADER;
		} else {
			/* new unknown field. we can't do anything about
			   this since we don't know its type */
			continue;
		}
		field.decision = updates[i].decision;
		if (updates[i].last_used != (time_t)-1)
			field.last_used = updates[i].last_used;
		array_append(&new_fields, &field, 1);
	}
	if (array_count(&new_fields) > 0) {
		mail_cache_register_fields(box->cache,
					   array_idx_modifiable(&new_fields, 0),
					   array_count(&new_fields));
	}
}
Example #14
0
static void
lmtp_client_rcpt_next(struct lmtp_client *client, const char *line)
{
	struct lmtp_rcpt *rcpt;
	enum lmtp_client_result result;

	result = line[0] == '2' ? LMTP_CLIENT_RESULT_OK :
		LMTP_CLIENT_RESULT_REMOTE_ERROR;
	if (result == LMTP_CLIENT_RESULT_OK)
		client->rcpt_to_successes = TRUE;

	rcpt = array_idx_modifiable(&client->recipients,
				    client->rcpt_next_receive_idx);
	client->rcpt_next_receive_idx++;

	rcpt->failed = result != LMTP_CLIENT_RESULT_OK;
	rcpt->rcpt_to_callback(result, line, rcpt->context);
}
Example #15
0
static void doveadm_print_formatted_print(const char *value)
{
	if (ctx.format == NULL) {
		i_fatal("formatted formatter cannot be used without a format.");
	}
	const char *error;
	struct var_expand_table *entry = array_idx_modifiable(&ctx.headers, ctx.idx++);
	entry->value = value;

	if (ctx.idx >= array_count(&ctx.headers)) {
		if (var_expand(ctx.buf, ctx.format, array_idx(&ctx.headers,0), &error) <= 0) {
			i_error("Failed to expand print format '%s': %s",
				ctx.format, error);
		}
		doveadm_print_formatted_flush();
		ctx.idx = 0;
	}

}
Example #16
0
void sieve_enotify_method_unregister
(const struct sieve_enotify_method *nmth)
{
	struct sieve_instance *svinst = nmth->svinst;
	const struct sieve_extension *ntfy_ext =
		sieve_extension_get_by_name(svinst, "enotify");

	if ( ntfy_ext != NULL ) {
		struct ext_enotify_context *ectx =
			(struct ext_enotify_context *) ntfy_ext->context;
		int nmth_id = nmth->id;

		if ( nmth_id >= 0 && nmth_id < (int)array_count(&ectx->notify_methods) ) {
			struct sieve_enotify_method *nmth_mod =
				array_idx_modifiable(&ectx->notify_methods, nmth_id);

			nmth_mod->def = NULL;
		}
	}
}
Example #17
0
static
void ldap_connection_abort_all_requests(struct ldap_connection *conn)
{
	struct ldap_result res;
	memset(&res, 0, sizeof(res));
	res.openldap_ret = LDAP_TIMEOUT;
	res.error_string = "Aborting LDAP requests due to failure";

	unsigned int n = aqueue_count(conn->request_queue);
	for (unsigned int i = 0; i < n; i++) {
		struct ldap_op_queue_entry **reqp =
			array_idx_modifiable(&(conn->request_array),
		aqueue_idx(conn->request_queue, i));
		if ((*reqp)->to_abort != NULL)
			timeout_remove(&(*reqp)->to_abort);
		if ((*reqp)->result_callback != NULL)
			(*reqp)->result_callback(&res, (*reqp)->result_callback_ctx);
		ldap_connection_request_destroy(reqp);
	}
	aqueue_clear(conn->request_queue);
}
Example #18
0
void io_loop_handle_add(struct io_file *io)
{
	struct ioloop_handler_context *ctx = io->io.ioloop->handler_context;
	struct io_list **list;
	struct epoll_event event;
	int op;
	bool first;

	list = array_idx_modifiable(&ctx->fd_index, io->fd);
	if (*list == NULL)
		*list = i_new(struct io_list, 1);

	first = ioloop_iolist_add(*list, io);

	memset(&event, 0, sizeof(event));
	event.data.ptr = *list;
	event.events = epoll_event_mask(*list);

	op = first ? EPOLL_CTL_ADD : EPOLL_CTL_MOD;

	if (epoll_ctl(ctx->epfd, op, io->fd, &event) < 0) {
		if (errno == EPERM && op == EPOLL_CTL_ADD) {
			i_fatal("epoll_ctl(add, %d) failed: %m "
				"(fd doesn't support epoll%s)", io->fd,
				io->fd != STDIN_FILENO ? "" :
				" - instead of '<file', try 'cat file|'");
		}
		i_panic("epoll_ctl(%s, %d) failed: %m",
			op == EPOLL_CTL_ADD ? "add" : "mod", io->fd);
	}

	if (first) {
		/* allow epoll_wait() to return the maximum number of events
		   by keeping space allocated for each file descriptor */
		if (ctx->deleted_count > 0)
			ctx->deleted_count--;
		else
			array_append_zero(&ctx->events);
	}
}
Example #19
0
void smtp_server_command_finished(struct smtp_server_command *cmd)
{
	struct smtp_server_connection *conn = cmd->context.conn;
	struct smtp_server_reply *reply;

	i_assert(cmd->state < SMTP_SERVER_COMMAND_STATE_FINISHED);
	cmd->state = SMTP_SERVER_COMMAND_STATE_FINISHED;

	DLLIST2_REMOVE(&conn->command_queue_head,
		       &conn->command_queue_tail, cmd);
	conn->command_queue_count--;
	conn->stats.reply_count++;

	i_assert(array_is_created(&cmd->replies));
	reply = array_idx_modifiable(&cmd->replies, 0);
	i_assert(reply->content != NULL);

	if (reply->content->status == 221 || reply->content->status == 421) {
		i_assert(cmd->replies_expected == 1);
		if (reply->content->status == 421) {
			smtp_server_connection_close(&conn, t_strdup_printf(
				"Server closed the connection: %s",
				smtp_server_reply_get_one_line(reply)));

		} else {
			smtp_server_connection_close(&conn,
				"Client has quit the connection");
		}
		smtp_server_command_unref(&cmd);
		return;
	} else if (cmd->input_locked) {
		if (cmd->input_captured)
			smtp_server_connection_input_halt(conn);
		smtp_server_connection_input_resume(conn);
	}

	smtp_server_command_unref(&cmd);
	smtp_server_connection_trigger_output(conn);
}
Example #20
0
char maildir_keywords_idx_char(struct maildir_keywords_sync_ctx *ctx,
			       unsigned int idx)
{
	const char *const *name_p;
	char *chr_p;
	unsigned int chridx;
	int ret;

	chr_p = array_idx_modifiable(&ctx->idx_to_chr, idx);
	if (*chr_p != '\0')
		return *chr_p;

	name_p = array_idx(ctx->keywords, idx);
	ret = !ctx->readonly ?
		maildir_keywords_lookup_or_create(ctx->mk, *name_p, &chridx) :
		maildir_keywords_lookup(ctx->mk, *name_p, &chridx);
	if (ret <= 0)
		return '\0';

	*chr_p = chridx + MAILDIR_KEYWORD_FIRST;
	return *chr_p;
}
Example #21
0
static void director_request_timeout(struct director *dir)
{
	struct director_request **requestp, *request;
	struct user *user;
	const char *errormsg;
	string_t *str = t_str_new(128);

	while (array_count(&dir->pending_requests) > 0) {
		requestp = array_idx_modifiable(&dir->pending_requests, 0);
		request = *requestp;

		if (request->create_time +
		    DIRECTOR_REQUEST_TIMEOUT_SECS > ioloop_time)
			break;

		user = user_directory_lookup(request->dir->users,
					     request->username_hash);
		errormsg = director_request_get_timeout_error(request,
							      user, str);
		if (user != NULL &&
		    request->delay_reason == REQUEST_DELAY_WEAK) {
			/* weakness appears to have gotten stuck. this is a
			   bug, but try to fix it for future requests by
			   removing the weakness. */
			user->weak = FALSE;
		}

		array_delete(&dir->pending_requests, 0, 1);
		T_BEGIN {
			request->callback(NULL, errormsg, request->context);
		} T_END;
		i_free(request);
	}

	if (array_count(&dir->pending_requests) == 0 && dir->to_request != NULL)
		timeout_remove(&dir->to_request);
}
Example #22
0
void io_loop_handle_remove(struct io_file *io, bool closed)
{
	struct ioloop_handler_context *ctx = io->io.ioloop->handler_context;
	struct io_list **list;
	struct epoll_event event;
	int op;
	bool last;

	list = array_idx_modifiable(&ctx->fd_index, io->fd);
	last = ioloop_iolist_del(*list, io);

	if (!closed) {
		memset(&event, 0, sizeof(event));
		event.data.ptr = *list;
		event.events = epoll_event_mask(*list);

		op = last ? EPOLL_CTL_DEL : EPOLL_CTL_MOD;

		if (epoll_ctl(ctx->epfd, op, io->fd, &event) < 0) {
			const char *errstr = t_strdup_printf(
				"epoll_ctl(%s, %d) failed: %m",
				op == EPOLL_CTL_DEL ? "del" : "mod", io->fd);
			if (errno == EBADF)
				i_panic("%s", errstr);
			else
				i_error("%s", errstr);
		}
	}
	if (last) {
		/* since we're not freeing memory in any case, just increase
		   deleted counter so next handle_add() can just decrease it
		   insteading of appending to the events array */
		ctx->deleted_count++;
	}
	i_free(io);
}
void auth_fields_add(struct auth_fields *fields,
		     const char *key, const char *value,
		     enum auth_field_flags flags)
{
	struct auth_field *field;
	unsigned int idx;

	i_assert(*key != '\0');
	i_assert(strchr(key, '\t') == NULL &&
		 strchr(key, '\n') == NULL);

	if (!auth_fields_find_idx(fields, key, &idx)) {
		if (!array_is_created(&fields->fields))
			p_array_init(&fields->fields, fields->pool, 16);

		field = array_append_space(&fields->fields);
		field->key = p_strdup(fields->pool, key);
	} else {
		auth_fields_snapshot_preserve(fields);
		field = array_idx_modifiable(&fields->fields, idx);
	}
	field->value = p_strdup_empty(fields->pool, value);
	field->flags = flags | AUTH_FIELD_FLAG_CHANGED;
}
Example #24
0
static bool
test_parse_header_line(struct test_parser *parser, struct test *test,
		       const char *line, const char **error_r)
{
	struct test_connection *test_conn;
	const char *key, *value;
	unsigned int idx;

	value = strchr(line, ':');
	if (value == NULL) {
		*error_r = "Missing ':'";
		return FALSE;
	}

	for (key = value; key[-1] == ' '; key--) ;
	key = t_str_lcase(t_strdup_until(line, key));
	for (value++; *value == ' '; value++) ;

	if (strcmp(key, "capabilities") == 0) {
		test->required_capabilities = (const char *const *)
			p_strsplit_spaces(parser->pool, value, " ");
		return TRUE;
	}
	if (strcmp(key, "connections") == 0) {
		test->connection_count = strcmp(value, "n") == 0 ? 2 :
			strtoul(value, NULL, 10);
		return TRUE;
	}
	if (strncmp(key, "user ", 5) == 0 &&
	    str_to_uint(key+5, &idx) == 0 && idx != 0) {
		/* FIXME: kludgy kludgy */
		if (strcmp(value, "$user2") == 0 ||
		    strcmp(value, "${user2}") == 0) {
			test->require_user2 = TRUE;
			value = conf.username2_template;
		}
		test_conn = array_idx_modifiable(&test->connections, idx-1);
		test_conn->username = p_strdup(parser->pool, value);
		return TRUE;
	}
	if (strcmp(key, "messages") == 0) {
		test->message_count = strcmp(value, "all") == 0 ? UINT_MAX :
			strtoul(value, NULL, 10);
		return TRUE;
	}
	if (strcmp(key, "state") == 0) {
		if (strcasecmp(value, "nonauth") == 0)
			test->startup_state = TEST_STARTUP_STATE_NONAUTH;
		else if (strcasecmp(value, "auth") == 0)
			test->startup_state = TEST_STARTUP_STATE_DELETED;
		else if (strcasecmp(value, "created") == 0)
			test->startup_state = TEST_STARTUP_STATE_CREATED;
		else if (strcasecmp(value, "appended") == 0)
			test->startup_state = TEST_STARTUP_STATE_APPENDED;
		else if (strcasecmp(value, "selected") == 0)
			test->startup_state = TEST_STARTUP_STATE_SELECTED;
		else {
			*error_r = "Unknown state value";
			return FALSE;
		}
		return TRUE;
	}

	*error_r = "Unknown setting";
	return FALSE;
}
static void thread_link_reference(struct mail_thread_cache *cache,
				  uint32_t parent_idx, uint32_t child_idx)
{
	struct mail_thread_node *node, *parent, *child;
	uint32_t idx;

	i_assert(parent_idx < cache->first_invalid_msgid_str_idx);

	/* either child_idx or parent_idx may cause thread_nodes array to
	   grow. in such situation the other pointer may become invalid if
	   we don't get the pointers in correct order. */
	if (child_idx < parent_idx) {
		parent = array_idx_modifiable(&cache->thread_nodes, parent_idx);
		child = array_idx_modifiable(&cache->thread_nodes, child_idx);
	} else {
		child = array_idx_modifiable(&cache->thread_nodes, child_idx);
		parent = array_idx_modifiable(&cache->thread_nodes, parent_idx);
	}

	child->parent_link_refcount++;
	if (thread_node_has_ancestor(cache, parent, child)) {
		if (parent == child) {
			/* loops to itself - ignore */
			return;
		}

		/* child is an ancestor of parent. Adding child -> parent_node
		   would introduce a loop. If any messages referencing the path
		   between parent_node's parent and child_node get expunged, we
		   have to rebuild the tree because the loop might break.
		   For example:

		     #1: a -> b       (a.ref=1, b.ref=1)
		     #2: b -> a       (a.ref=2, b.ref=2)
		     #3: c -> a -> b  (a.ref=3, b.ref=3, c.ref=1)

		   Expunging #3 wouldn't break the loop, but expunging #1
		   would. */
		node = parent;
		do {
			idx = node->parent_idx;
			i_assert(idx != 0);
			node = array_idx_modifiable(&cache->thread_nodes, idx);
			node->child_unref_rebuilds = TRUE;
		} while (node != child);
		return;
	} else if (child->parent_idx == parent_idx) {
		/* The same link already exists */
		return;
	}

	/* Set parent_node as child_node's parent */
	if (child->parent_idx == 0) {
		child->parent_idx = parent_idx;
	} else {
		/* Conflicting parent already exists, keep the original */
		if (MAIL_THREAD_NODE_EXISTS(child)) {
			/* If this message gets expunged,
			   the parent is changed. */
			child->expunge_rebuilds = TRUE;
		} else {
			/* Message doesn't exist, so it was one of the node's
			   children that created the original reference. If
			   that reference gets dropped, the parent is changed.
			   We could catch this in one of several ways:

			    a) Link to parent node gets unreferenced
			    b) Link to this node gets unreferenced
			    c) Any of the child nodes gets expunged

			   b) is probably the least likely to happen,
			   so use it */
			child->child_unref_rebuilds = TRUE;
		}
	}
}
Example #26
0
static int maildir_keywords_sync(struct maildir_keywords *mk)
{
	struct istream *input;
	struct stat st;
	char *line, *p, *new_name;
	const char **strp;
	unsigned int idx;
	int fd;

        /* Remember that we rely on uidlist file locking in here. That's why
           we rely on stat()'s timestamp and don't bother handling ESTALE
           errors. */

	if (mk->storage->set->mail_nfs_storage) {
		/* file is updated only by replacing it, no need to flush
		   attribute cache */
		nfs_flush_file_handle_cache(mk->path);
	}

	if (nfs_safe_stat(mk->path, &st) < 0) {
		if (errno == ENOENT) {
			maildir_keywords_clear(mk);
			mk->synced = TRUE;
			return 0;
		}
                mail_storage_set_critical(mk->storage,
					  "stat(%s) failed: %m", mk->path);
		return -1;
	}

	if (st.st_mtime == mk->synced_mtime) {
		/* hasn't changed */
		mk->synced = TRUE;
		return 0;
	}
	mk->synced_mtime = st.st_mtime;

	fd = open(mk->path, O_RDONLY);
	if (fd == -1) {
		if (errno == ENOENT) {
			maildir_keywords_clear(mk);
			mk->synced = TRUE;
			return 0;
		}
                mail_storage_set_critical(mk->storage,
					  "open(%s) failed: %m", mk->path);
		return -1;
	}

	maildir_keywords_clear(mk);
	input = i_stream_create_fd(fd, 1024, FALSE);
	while ((line = i_stream_read_next_line(input)) != NULL) {
		p = strchr(line, ' ');
		if (p == NULL) {
			/* note that when converting .customflags file this
			   case happens in the first line. */
			continue;
		}
		*p++ = '\0';

		if (str_to_uint(line, &idx) < 0 ||
		    idx >= MAILDIR_MAX_KEYWORDS || *p == '\0') {
			/* shouldn't happen */
			continue;
		}

		/* save it */
		new_name = p_strdup(mk->pool, p);
		hash_table_insert(mk->hash, new_name, POINTER_CAST(idx + 1));

		strp = array_idx_modifiable(&mk->list, idx);
		*strp = new_name;
	}
	i_stream_destroy(&input);

	if (close(fd) < 0) {
                mail_storage_set_critical(mk->storage,
					  "close(%s) failed: %m", mk->path);
		return -1;
	}

	mk->synced = TRUE;
	return 0;
}