Ejemplo n.º 1
0
static void
cydir_sync_expunge(struct cydir_sync_context *ctx, uint32_t seq1, uint32_t seq2)
{
	struct mailbox *box = &ctx->mbox->box;
	uint32_t uid;

	if (ctx->path == NULL) {
		ctx->path = cydir_get_path_prefix(ctx->mbox);
		ctx->path_dir_prefix_len = str_len(ctx->path);
	}

	for (; seq1 <= seq2; seq1++) {
		mail_index_lookup_uid(ctx->sync_view, seq1, &uid);

		str_truncate(ctx->path, ctx->path_dir_prefix_len);
		str_printfa(ctx->path, "%u.", uid);
		if (i_unlink_if_exists(str_c(ctx->path)) < 0) {
			/* continue anyway */
		} else {
			if (box->v.sync_notify != NULL) {
				box->v.sync_notify(box, uid,
						   MAILBOX_SYNC_TYPE_EXPUNGE);
			}
			mail_index_expunge(ctx->trans, seq1);
		}
	}
}
Ejemplo n.º 2
0
static int
try_create_new(const char *path, const struct file_create_settings *set,
	       int *fd_r, struct file_lock **lock_r,  const char **error_r)
{
	string_t *temp_path = t_str_new(128);
	int fd, orig_errno, ret = -1;
	int mode = set->mode != 0 ? set->mode : 0600;
	uid_t uid = set->uid != 0 ? set->uid : (uid_t)-1;
	uid_t gid = set->gid != 0 ? set->gid : (gid_t)-1;

	str_append(temp_path, path);
	if (uid != (uid_t)-1)
		fd = safe_mkstemp(temp_path, mode, uid, gid);
	else
		fd = safe_mkstemp_group(temp_path, mode, gid, set->gid_origin);
	if (fd == -1) {
		*error_r = t_strdup_printf("safe_mkstemp(%s) failed: %m", path);
		return -1;
	}
	if (file_try_lock_error(fd, str_c(temp_path), F_WRLCK,
				set->lock_method, lock_r, error_r) <= 0) {
	} else if (link(str_c(temp_path), path) < 0) {
		if (errno == EEXIST) {
			/* just created by somebody else */
			ret = 0;
		} else if (errno == ENOENT) {
			/* our temp file was just deleted by somebody else,
			   retry creating it. */
			ret = 0;
		} else {
			*error_r = t_strdup_printf("link(%s, %s) failed: %m",
						   str_c(temp_path), path);
		}
		file_lock_free(lock_r);
	} else {
		i_unlink_if_exists(str_c(temp_path));
		*fd_r = fd;
		return 1;
	}
	orig_errno = errno;
	i_close_fd(&fd);
	i_unlink_if_exists(str_c(temp_path));
	errno = orig_errno;
	return ret;
}
Ejemplo n.º 3
0
static int maildir_keywords_commit(struct maildir_keywords *mk)
{
	const struct mailbox_permissions *perm;
	struct dotlock *dotlock;
	const char *lock_path;
	mode_t old_mask;
	int i, fd;

	mk->synced = FALSE;

	if (!mk->changed || mk->mbox == NULL)
		return 0;

	lock_path = t_strconcat(mk->path, ".lock", NULL);
	i_unlink_if_exists(lock_path);

	perm = mailbox_get_permissions(&mk->mbox->box);
	for (i = 0;; i++) {
		/* we could just create the temp file directly, but doing it
		   this ways avoids potential problems with overwriting
		   contents in malicious symlinks */
		old_mask = umask(0777 & ~perm->file_create_mode);
		fd = file_dotlock_open(&mk->dotlock_settings, mk->path,
				       DOTLOCK_CREATE_FLAG_NONBLOCK, &dotlock);
		umask(old_mask);
		if (fd != -1)
			break;

		if (errno != ENOENT || i == MAILDIR_DELETE_RETRY_COUNT) {
			mail_storage_set_critical(mk->storage,
				"file_dotlock_open(%s) failed: %m", mk->path);
			return -1;
		}
		/* the control dir doesn't exist. create it unless the whole
		   mailbox was just deleted. */
		if (!maildir_set_deleted(&mk->mbox->box))
			return -1;
	}

	if (maildir_keywords_write_fd(mk, lock_path, fd) < 0) {
		file_dotlock_delete(&dotlock);
		return -1;
	}

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

	mk->changed = FALSE;
	return 0;
}
Ejemplo n.º 4
0
static void mail_transaction_log_2_unlink_old(struct mail_transaction_log *log)
{
	struct stat st;

	if (MAIL_INDEX_IS_IN_MEMORY(log->index))
		return;

	if (stat(log->filepath2, &st) < 0) {
		if (errno != ENOENT && errno != ESTALE) {
			mail_index_set_error(log->index,
				"stat(%s) failed: %m", log->filepath2);
		}
		return;
	}

	if (ioloop_time - st.st_mtime >= (time_t)log->index->log_rotate_log2_stale_secs &&
	    !log->index->readonly)
		i_unlink_if_exists(log->filepath2);
}
Ejemplo n.º 5
0
void squat_uidlist_delete(struct squat_uidlist *uidlist)
{
	i_unlink_if_exists(uidlist->path);
}
Ejemplo n.º 6
0
static uint32_t
mailbox_uidvalidity_next_rescan(struct mailbox_list *list, const char *path)
{
	DIR *d;
	struct dirent *dp;
	const char *fname, *dir, *prefix, *tmp;
	unsigned int i, prefix_len;
	uint32_t cur_value, min_value, max_value;
	mode_t old_mask;
	int fd;

	fname = strrchr(path, '/');
	if (fname == NULL) {
		dir = ".";
		fname = path;
	} else {
		dir = t_strdup_until(path, fname);
		fname++;
	}

	d = opendir(dir);
	if (d == NULL && errno == ENOENT) {
		/* FIXME: the PATH_TYPE_CONTROL should come as a parameter, but
		   that's an API change, do it in v2.3. it's not really a
		   problem though, since currently all backends use control
		   dirs for the uidvalidity file. */
		(void)mailbox_list_mkdir_root(list, dir, MAILBOX_LIST_PATH_TYPE_CONTROL);
		d = opendir(dir);
	}
	if (d == NULL) {
		i_error("opendir(%s) failed: %m", dir);
		return mailbox_uidvalidity_next_fallback();
	}
	prefix = t_strconcat(fname, ".", NULL);
	prefix_len = strlen(prefix);

	/* just in case there happens to be multiple matching uidvalidity
	   files, track the min/max values. use the max value and delete the
	   min value file. */
	max_value = 0; min_value = (uint32_t)-1;
	while ((dp = readdir(d)) != NULL) {
		if (strncmp(dp->d_name, prefix, prefix_len) == 0) {
			if (str_to_uint32_hex(dp->d_name + prefix_len, &cur_value) >= 0) {
				if (min_value > cur_value)
					min_value = cur_value;
				if (max_value < cur_value)
					max_value = cur_value;
			}
		}
	}
	if (closedir(d) < 0)
		i_error("closedir(%s) failed: %m", dir);

	if (max_value == 0) {
		/* no uidvalidity files. create one. */
		for (i = 0; i < RETRY_COUNT; i++) {
			cur_value = mailbox_uidvalidity_next_fallback();
			tmp = t_strdup_printf("%s.%08x", path, cur_value);
			/* the file is empty, don't bother with permissions */
			old_mask = umask(0);
			fd = open(tmp, O_RDWR | O_CREAT | O_EXCL, 0444);
			umask(old_mask);
			if (fd != -1 || errno != EEXIST)
				break;
			/* already exists. although it's quite unlikely we'll
			   hit this race condition. more likely we'll create
			   a duplicate file.. */
		}
		if (fd == -1) {
			i_error("creat(%s) failed: %m", tmp);
			return cur_value;
		}
		i_close_fd(&fd);
		mailbox_uidvalidity_write(list, path, cur_value);
		return cur_value;
	}
	if (min_value != max_value) {
		/* duplicate uidvalidity files, delete the oldest */
		tmp = t_strdup_printf("%s.%08x", path, min_value);
		i_unlink_if_exists(tmp);
	}

	cur_value = max_value;
	if (mailbox_uidvalidity_rename(path, &cur_value, TRUE) < 0)
		return mailbox_uidvalidity_next_fallback();
	mailbox_uidvalidity_write(list, path, cur_value);
	return cur_value;
}
Ejemplo n.º 7
0
int mail_transaction_log_file_open(struct mail_transaction_log_file *file)
{
	struct mail_index *index = file->log->index;
        unsigned int i;
	bool ignore_estale;
	int ret;

        for (i = 0;; i++) {
		if (!index->readonly) {
			file->fd = nfs_safe_open(file->filepath,
						 O_RDWR | O_APPEND);
		} else {
			file->fd = nfs_safe_open(file->filepath, O_RDONLY);
		}
		if (file->fd == -1 && errno == EACCES) {
			file->fd = nfs_safe_open(file->filepath, O_RDONLY);
			index->readonly = TRUE;
		}
		if (file->fd == -1) {
			if (errno == ENOENT)
				return 0;

			log_file_set_syscall_error(file, "open()");
			return -1;
                }

		ignore_estale = i < MAIL_INDEX_ESTALE_RETRY_COUNT;
		if (mail_transaction_log_file_stat(file, ignore_estale) < 0)
			ret = -1;
		else if (mail_transaction_log_file_is_dupe(file)) {
			/* probably our already opened .log file has been
			   renamed to .log.2 and we're trying to reopen it.
			   also possible that hit a race condition where .log
			   and .log.2 are linked. */
			return 0;
		} else {
			ret = mail_transaction_log_file_read_hdr(file,
								 ignore_estale);
		}
		if (ret > 0) {
			/* success */
			break;
		}

		if (ret == 0) {
			/* corrupted */
			if (index->readonly) {
				/* don't delete */
			} else {
				i_unlink_if_exists(file->filepath);
			}
			return 0;
		}
		if (errno != ESTALE ||
		    i == MAIL_INDEX_ESTALE_RETRY_COUNT) {
			/* syscall error */
			return -1;
		}

		/* ESTALE - try again */
		buffer_free(&file->buffer);
        }

	mail_transaction_log_file_add_to_list(file);
	return 1;
}
Ejemplo n.º 8
0
static int
mail_transaction_log_file_create2(struct mail_transaction_log_file *file,
				  int new_fd, bool reset,
				  struct dotlock **dotlock)
{
	struct mail_index *index = file->log->index;
	struct stat st;
	const char *path2;
	buffer_t *writebuf;
	int fd, ret;
	bool rename_existing, need_lock;

	need_lock = file->log->head != NULL && file->log->head->locked;

	if (fcntl(new_fd, F_SETFL, O_APPEND) < 0) {
		log_file_set_syscall_error(file, "fcntl(O_APPEND)");
		return -1;
	}

	if (file->log->nfs_flush) {
		/* although we check also mtime and file size below, it's done
		   only to fix broken log files. we don't bother flushing
		   attribute cache just for that. */
		nfs_flush_file_handle_cache(file->filepath);
	}

	/* log creation is locked now - see if someone already created it.
	   note that if we're rotating, we need to keep the log locked until
	   the file has been rewritten. and because fcntl() locks are stupid,
	   if we go and open()+close() the file and we had it already opened,
	   its locks are lost. so we use stat() to check if the file has been
	   recreated, although it almost never is. */
	if (reset)
		rename_existing = FALSE;
	else if (nfs_safe_stat(file->filepath, &st) < 0) {
		if (errno != ENOENT) {
			log_file_set_syscall_error(file, "stat()");
			return -1;
		}
		rename_existing = FALSE;
	} else if (st.st_ino == file->st_ino &&
		   CMP_DEV_T(st.st_dev, file->st_dev) &&
		   /* inode/dev checks are enough when we're rotating the file,
		      but not when we're replacing a broken log file */
		   st.st_mtime == file->last_mtime &&
		   (uoff_t)st.st_size == file->last_size) {
		/* no-one else recreated the file */
		rename_existing = TRUE;
	} else {
		/* recreated. use the file if its header is ok */
		fd = nfs_safe_open(file->filepath, O_RDWR | O_APPEND);
		if (fd == -1) {
			if (errno != ENOENT) {
				log_file_set_syscall_error(file, "open()");
				return -1;
			}
		} else {
			file->fd = fd;
			file->last_size = 0;
			if (mail_transaction_log_file_read_hdr(file,
							       FALSE) > 0 &&
			    mail_transaction_log_file_stat(file, FALSE) == 0) {
				/* yes, it was ok */
				file_dotlock_delete(dotlock);
				mail_transaction_log_file_add_to_list(file);
				return 0;
			}
			file->fd = -1;
			if (close(fd) < 0)
				log_file_set_syscall_error(file, "close()");
		}
		rename_existing = FALSE;
	}

	if (index->fd == -1 && !rename_existing) {
		/* creating the initial index */
		reset = TRUE;
	}

	if (mail_transaction_log_init_hdr(file->log, &file->hdr) < 0)
		return -1;

	if (reset) {
		/* don't reset modseqs. if we're reseting due to rebuilding
		   indexes we'll probably want to keep uidvalidity and in such
		   cases we really don't want to shrink modseqs. */
		file->hdr.prev_file_seq = 0;
		file->hdr.prev_file_offset = 0;
	}

	writebuf = buffer_create_dynamic(pool_datastack_create(), 128);
	buffer_append(writebuf, &file->hdr, sizeof(file->hdr));

	if (index->ext_hdr_init_data != NULL && reset)
		log_write_ext_hdr_init_data(index, writebuf);
	if (write_full(new_fd, writebuf->data, writebuf->used) < 0) {
		log_file_set_syscall_error(file, "write_full()");
		return -1;
	}

	if (file->log->index->fsync_mode == FSYNC_MODE_ALWAYS) {
		/* the header isn't important, so don't bother calling
		   fdatasync() unless it's required */
		if (fdatasync(new_fd) < 0) {
			log_file_set_syscall_error(file, "fdatasync()");
			return -1;
		}
	}

	file->fd = new_fd;
	ret = mail_transaction_log_file_stat(file, FALSE);

	if (need_lock) {
		/* we'll need to preserve the lock */
		if (mail_transaction_log_file_lock(file) < 0)
			ret = -1;
	}

	/* if we return -1 the dotlock deletion code closes the fd */
	file->fd = -1;
	if (ret < 0)
		return -1;

	/* keep two log files */
	if (rename_existing) {
		/* rename() would be nice and easy way to do this, except then
		   there's a race condition between the rename and
		   file_dotlock_replace(). during that time the log file
		   doesn't exist, which could cause problems. */
		path2 = t_strconcat(file->filepath, ".2", NULL);
		if (i_unlink_if_exists(path2) < 0) {
			/* try to link() anyway */
		}
		if (nfs_safe_link(file->filepath, path2, FALSE) < 0 &&
		    errno != ENOENT && errno != EEXIST) {
                        mail_index_set_error(index, "link(%s, %s) failed: %m",
					     file->filepath, path2);
			/* ignore the error. we don't care that much about the
			   second log file and we're going to overwrite this
			   first one. */
		}
		/* NOTE: here's a race condition where both .log and .log.2
		   point to the same file. our reading code should ignore that
		   though by comparing the inodes. */
	}

	if (file_dotlock_replace(dotlock,
				 DOTLOCK_REPLACE_FLAG_DONT_CLOSE_FD) <= 0)
		return -1;

	/* success */
	file->fd = new_fd;
	mail_transaction_log_file_add_to_list(file);

	i_assert(!need_lock || file->locked);
	return 1;
}
Ejemplo n.º 9
0
static int maildir_save_finish_real(struct mail_save_context *_ctx)
{
	struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx;
	struct mail_storage *storage = &ctx->mbox->storage->storage;
	const char *path;
	off_t real_size;
	uoff_t size;
	int output_errno;

	ctx->last_save_finished = TRUE;
	if (ctx->failed && ctx->fd == -1) {
		/* tmp file creation failed */
		return -1;
	}

	path = t_strconcat(ctx->tmpdir, "/", ctx->file_last->tmp_name, NULL);
	if (!ctx->failed && o_stream_nfinish(_ctx->data.output) < 0) {
		if (!mail_storage_set_error_from_errno(storage)) {
			mail_storage_set_critical(storage,
				"write(%s) failed: %m", path);
		}
		ctx->failed = TRUE;
	}

	if (_ctx->data.save_date != (time_t)-1) {
		/* we can't change ctime, but we can add the date to cache */
		struct index_mail *mail = (struct index_mail *)_ctx->dest_mail;
		uint32_t t = _ctx->data.save_date;

		index_mail_cache_add(mail, MAIL_CACHE_SAVE_DATE, &t, sizeof(t));
	}

 	if (maildir_save_finish_received_date(ctx, path) < 0)
		ctx->failed = TRUE;

	if (ctx->cur_dest_mail != NULL) {
		index_mail_cache_parse_deinit(ctx->cur_dest_mail,
					      ctx->ctx.data.received_date,
					      !ctx->failed);
	}
	i_stream_unref(&ctx->input);

	/* remember the size in case we want to add it to filename */
	ctx->file_last->size = _ctx->data.output->offset;
	if (ctx->cur_dest_mail == NULL ||
	    mail_get_virtual_size(ctx->cur_dest_mail,
				  &ctx->file_last->vsize) < 0)
		ctx->file_last->vsize = (uoff_t)-1;

	output_errno = _ctx->data.output->last_failed_errno;
	o_stream_destroy(&_ctx->data.output);

	if (storage->set->parsed_fsync_mode != FSYNC_MODE_NEVER &&
	    !ctx->failed) {
		if (fsync(ctx->fd) < 0) {
			if (!mail_storage_set_error_from_errno(storage)) {
				mail_storage_set_critical(storage,
						  "fsync(%s) failed: %m", path);
			}
			ctx->failed = TRUE;
		}
	}
	real_size = lseek(ctx->fd, 0, SEEK_END);
	if (real_size == (off_t)-1) {
		mail_storage_set_critical(storage,
					  "lseek(%s) failed: %m", path);
	} else if (real_size != (off_t)ctx->file_last->size &&
		   (!maildir_filename_get_size(ctx->file_last->dest_basename,
					       MAILDIR_EXTRA_FILE_SIZE, &size) ||
		    size != ctx->file_last->size)) {
		/* e.g. zlib plugin was used. the "physical size" must be in
		   the maildir filename, since stat() will return wrong size */
		ctx->file_last->preserve_filename = FALSE;
		/* preserve the GUID if needed */
		if (ctx->file_last->guid == NULL)
			ctx->file_last->guid = ctx->file_last->dest_basename;
		/* reset the base name as well, just in case there's a
		   ,W=vsize */
		ctx->file_last->dest_basename = ctx->file_last->tmp_name;
	}
	if (close(ctx->fd) < 0) {
		if (!mail_storage_set_error_from_errno(storage)) {
			mail_storage_set_critical(storage,
						  "close(%s) failed: %m", path);
		}
		ctx->failed = TRUE;
	}
	ctx->fd = -1;

	if (ctx->failed) {
		/* delete the tmp file */
		i_unlink_if_exists(path);

		errno = output_errno;
		if (ENOQUOTA(errno)) {
			mail_storage_set_error(storage,
				MAIL_ERROR_NOQUOTA, MAIL_ERRSTR_NO_QUOTA);
		} else if (errno != 0) {
			mail_storage_set_critical(storage,
				"write(%s) failed: %m", path);
		}

		maildir_save_remove_last_filename(ctx);
		return -1;
	}

	ctx->file_last = NULL;
	return 0;
}
Ejemplo n.º 10
0
static int acl_backend_vfile_acllist_read(struct acl_backend_vfile *backend)
{
	struct acl_backend_vfile_acllist acllist;
	struct istream *input;
	struct stat st;
	const char *path, *line, *p;
	int fd, ret = 0;

	backend->acllist_last_check = ioloop_time;

	if (!acl_list_get_path(backend, &path)) {
		/* we're never going to build acllist for this namespace. */
		acllist_clear(backend, 0);
		return 0;
	}

	if (backend->acllist_mtime != 0) {
		/* see if the file's mtime has changed */
		if (stat(path, &st) < 0) {
			if (errno == ENOENT)
				backend->acllist_mtime = 0;
			else
				i_error("stat(%s) failed: %m", path);
			return -1;
		}
		if (st.st_mtime == backend->acllist_mtime)
			return 0;
	}

	fd = open(path, O_RDONLY);
	if (fd == -1) {
		if (errno == ENOENT) {
			backend->acllist_mtime = 0;
			return -1;
		}
		i_error("open(%s) failed: %m", path);
		return -1;
	}
	if (fstat(fd, &st) < 0) {
		i_error("fstat(%s) failed: %m", path);
		i_close_fd(&fd);
		return -1;
	}
	backend->acllist_mtime = st.st_mtime;
	acllist_clear(backend, st.st_size);

	input = i_stream_create_fd(fd, (size_t)-1, FALSE);
	while ((line = i_stream_read_next_line(input)) != NULL) {
		acllist.mtime = 0;
		for (p = line; *p >= '0' && *p <= '9'; p++)
			acllist.mtime = acllist.mtime * 10 + (*p - '0');

		if (p == line || *p != ' ' || p[1] == '\0') {
			i_error("Broken acllist file: %s", path);
			i_unlink_if_exists(path);
			i_close_fd(&fd);
			return -1;
		}
		acllist.name = p_strdup(backend->acllist_pool, p + 1);
		array_append(&backend->acllist, &acllist, 1);
	}
	if (input->stream_errno != 0)
		ret = -1;
	i_stream_destroy(&input);

	if (close(fd) < 0)
		i_error("close(%s) failed: %m", path);
	return ret;
}