Cache open/stat for performance

Open fd stays valid for 60s.  It will actually stick around forever,
there is no garbage collection for stale files yet.

Signed-off-by: Joern Engel <joern@logfs.org>
diff --git a/cancd.c b/cancd.c
index ebd8bdd..4538f6c 100644
--- a/cancd.c
+++ b/cancd.c
@@ -105,6 +105,9 @@
 	const char *filename;
 	const char *tmpfilename;
 	int had_newline;
+	int fd;
+	uint64_t filesize;
+	uint64_t reopen_time;
 };
 
 void handler(int signum)
@@ -322,7 +325,7 @@
 	return 0;
 }
 
-static int write_formatted(int fd, struct source_ip *sip, char *buf, int count)
+static int write_formatted(struct source_ip *sip, char *buf, int count)
 {
 	const char *format = "%b %d %H:%M:%S ";
 	char *end;
@@ -340,19 +343,21 @@
 	while (count > 1) {
 		if (sip->had_newline) {
 			n = strftime(timestr, sizeof(timestr), format, tm);
-			err = do_write(fd, timestr, n);
+			err = do_write(sip->fd, timestr, n);
 			if (err)
 				return err;
 		}
 		end = strchr(buf, 0xa);
 		if (!end) {
 			/* no newline, just write what we have */
-			do_write(fd, buf, count - 1);
+			err = do_write(sip->fd, buf, count - 1);
+			if (err)
+				return err;
 			sip->had_newline = 0;
 			break;
 		}
 		len = end - buf + 1;
-		err = do_write(fd, buf, len);
+		err = do_write(sip->fd, buf, len);
 		if (err)
 			return err;
 		buf += len;
@@ -372,6 +377,7 @@
 	if (!sip) {
 		sip = calloc(1, sizeof(*sip));
 		sip->had_newline = 1;
+		sip->fd = -1;
 		sip->tmpfilename = get_path(&addr->sin_addr);
 		err = btree_insert32(&btree, key, sip);
 		assert(!err);
@@ -398,6 +404,10 @@
 		char buf[4096];
 		ssize_t count;
 
+		if (sip->fd >= 0) {
+			close(sip->fd);
+			sip->fd = -1;
+		}
 		fd = open(name, O_WRONLY | O_APPEND | O_CREAT, 0644);
 		if (fd < 0) {
 			syslog(LOG_ERR, "Unable to open \"%s\": %s", name,
@@ -427,9 +437,17 @@
 	return NULL;
 }
 
+uint64_t gethrtime(void)
+{
+	struct timespec tod;
+
+	clock_gettime(CLOCK_MONOTONIC, &tod);
+	return tod.tv_sec * 1000000000ULL + tod.tv_nsec;
+}
+
 static void do_output(char *buf, int len, struct sockaddr_in *addr, socklen_t socklen)
 {
-	int fd, err;
+	int err;
 	struct stat stat;
 	const char *name;
 	struct source_ip *sip = get_source_ip(addr);
@@ -438,17 +456,35 @@
 	if (!name)
 		return;
 
-	fd = open(name, O_WRONLY | O_APPEND | O_CREAT, 0644);
-	if (fd < 0) {
-		syslog(LOG_ERR, "Unable to open \"%s\": %s", name, strerror(errno));
-		return;
+	/*
+	 * We cache the open fd for 60s.  We also cache the filesize.
+	 * This reduces cpu overhead a bit, but means logrotate has to
+	 * keep the file around for 60s after rotation and we can
+	 * overshoot the size limit a bit.
+	 */
+	if (sip->fd >= 0 && gethrtime() > sip->reopen_time) {
+		close(sip->fd);
+		sip->fd = -1;
 	}
-	err = fstat(fd, &stat);
-	if (err || stat.st_size > size_limit)
-		goto out;
-	write_formatted(fd, sip, buf, len);
-out:
-	close(fd);
+
+	if (sip->fd < 0) {
+		sip->fd = open(name, O_WRONLY | O_APPEND | O_CREAT, 0644);
+		if (sip->fd < 0) {
+			syslog(LOG_ERR, "Unable to open \"%s\": %s", name, strerror(errno));
+			return;
+		}
+		err = fstat(sip->fd, &stat);
+		if (err) {
+			close(sip->fd);
+			sip->fd = -1;
+			return;
+		}
+		sip->filesize = stat.st_size;
+		sip->reopen_time = gethrtime() + 60000000000ULL; /* 60s */
+	}
+	if (sip->filesize > size_limit)
+		return;
+	write_formatted(sip, buf, len);
 }
 
 static int set_blocking(int blocking)