0N/A/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
0N/A
0N/A#include "lib.h"
0N/A#include "ioloop.h"
0N/A#include "str.h"
0N/A#include "master-service.h"
0N/A#include "stats-connection.h"
0N/A
0N/A#include <unistd.h>
0N/A#include <fcntl.h>
0N/A
0N/A#define STATS_EAGAIN_WARN_INTERVAL_SECS 30
0N/A
0N/Astruct stats_connection {
0N/A int refcount;
0N/A
0N/A int fd;
0N/A char *path;
0N/A
0N/A bool open_failed;
0N/A time_t next_warning_timestamp;
0N/A};
0N/A
0N/Astatic bool stats_connection_open(struct stats_connection *conn)
0N/A{
0N/A if (conn->open_failed)
0N/A return FALSE;
0N/A
0N/A conn->fd = open(conn->path, O_WRONLY | O_NONBLOCK);
0N/A if (conn->fd == -1) {
0N/A i_error("stats: open(%s) failed: %m", conn->path);
0N/A conn->open_failed = TRUE;
0N/A return FALSE;
0N/A }
0N/A return TRUE;
0N/A}
0N/A
0N/Astruct stats_connection *
0N/Astats_connection_create(const char *path)
0N/A{
0N/A struct stats_connection *conn;
0N/A
0N/A conn = i_new(struct stats_connection, 1);
0N/A conn->refcount = 1;
0N/A conn->path = i_strdup(path);
0N/A (void)stats_connection_open(conn);
0N/A return conn;
0N/A}
0N/A
0N/Avoid stats_connection_ref(struct stats_connection *conn)
0N/A{
0N/A conn->refcount++;
0N/A}
0N/A
0N/Avoid stats_connection_unref(struct stats_connection **_conn)
0N/A{
0N/A struct stats_connection *conn = *_conn;
0N/A
0N/A i_assert(conn->refcount > 0);
0N/A if (--conn->refcount > 0)
0N/A return;
0N/A
0N/A *_conn = NULL;
0N/A i_close_fd_path(&conn->fd, conn->path);
0N/A i_free(conn->path);
0N/A i_free(conn);
0N/A}
0N/A
0N/Aint stats_connection_send(struct stats_connection *conn, const string_t *str)
0N/A{
0N/A static bool pipe_warned = FALSE;
0N/A ssize_t ret;
0N/A
0N/A /* if master process has been stopped (and restarted), don't even try
to notify the stats process anymore. even if one exists, it doesn't
know about us. */
if (master_service_is_master_stopped(master_service))
return -1;
if (conn->fd == -1) {
if (!stats_connection_open(conn))
return -1;
i_assert(conn->fd != -1);
}
if (str_len(str) > PIPE_BUF && !pipe_warned) {
i_warning("stats update sent more bytes that PIPE_BUF "
"(%"PRIuSIZE_T" > %u), this may break statistics",
str_len(str), (unsigned int)PIPE_BUF);
pipe_warned = TRUE;
}
ret = write(conn->fd, str_data(str), str_len(str));
if (ret == (ssize_t)str_len(str)) {
/* success */
return 0;
} else if (ret < 0 && errno == EAGAIN) {
/* stats process is busy */
if (ioloop_time > conn->next_warning_timestamp) {
i_warning("write(%s) failed: %m (stats process is busy)", conn->path);
conn->next_warning_timestamp = ioloop_time +
STATS_EAGAIN_WARN_INTERVAL_SECS;
}
return -1;
} else {
/* error - reconnect */
if (ret < 0) {
/* don't log EPIPE errors. they can happen when
Dovecot is stopped. */
if (errno != EPIPE)
i_error("write(%s) failed: %m", conn->path);
} else if ((size_t)ret != str_len(str))
i_error("write(%s) wrote partial update", conn->path);
if (close(conn->fd) < 0)
i_error("close(%s) failed: %m", conn->path);
conn->fd = -1;
return -1;
}
}