about summary refs log tree commit diff
path: root/log.c
diff options
context:
space:
mode:
Diffstat (limited to 'log.c')
-rw-r--r--log.c224
1 files changed, 118 insertions, 106 deletions
diff --git a/log.c b/log.c
index 2681bac..d6b3f2a 100644
--- a/log.c
+++ b/log.c
@@ -1,157 +1,169 @@
-/* Copyright (C) 2018  C. McEnroe <june@causal.agency>
+/* Copyright (C) 2020  June McEnroe <june@causal.agency>
  *
  * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
+ * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation, either version 3 of the License, or
  * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
+ * GNU General Public License for more details.
  *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ * Additional permission under GNU GPL version 3 section 7:
+ *
+ * If you modify this Program, or any covered work, by linking or
+ * combining it with OpenSSL (or a modified version of that library),
+ * containing parts covered by the terms of the OpenSSL License and the
+ * original SSLeay license, the licensors of this Program grant you
+ * additional permission to convey the resulting work. Corresponding
+ * Source for a non-source form of such a combination shall include the
+ * source code for the parts of OpenSSL used as well as that of the
+ * covered work.
  */
 
+#include <assert.h>
 #include <err.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <limits.h>
+#include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <string.h>
 #include <sys/stat.h>
 #include <sysexits.h>
 #include <time.h>
+#include <unistd.h>
+
+#ifdef __FreeBSD__
+#include <capsicum_helpers.h>
+#endif
 
 #include "chat.h"
 
-static int logRoot = -1;
+static int logDir = -1;
 
-static struct Log {
-	int dir;
-	int year;
-	int month;
-	int day;
-	FILE *file;
-} logs[TagsLen];
+void logOpen(void) {
+	char buf[PATH_MAX];
+	int error = mkdir(dataPath(buf, sizeof(buf), "", 0), S_IRWXU);
+	if (error && errno != EEXIST) err(EX_CANTCREAT, "%s", buf);
+
+	error = mkdir(dataPath(buf, sizeof(buf), "log", 0), S_IRWXU);
+	if (error && errno != EEXIST) err(EX_CANTCREAT, "%s", buf);
+
+	logDir = open(buf, O_RDONLY | O_CLOEXEC);
+	if (logDir < 0) err(EX_CANTCREAT, "%s", buf);
 
-void logOpen(const char *path) {
-	logRoot = open(path, O_RDONLY | O_CLOEXEC);
-	if (logRoot < 0) err(EX_CANTCREAT, "%s", path);
+#ifdef __FreeBSD__
+	cap_rights_t rights;
+	cap_rights_init(
+		&rights, CAP_MKDIRAT, CAP_CREATE, CAP_WRITE,
+		/* for fdopen(3) */ CAP_FCNTL, CAP_FSTAT
+	);
+	error = caph_rights_limit(logDir, &rights);
+	if (error) err(EX_OSERR, "cap_rights_limit");
+#endif
+}
+
+static void logMkdir(const char *path) {
+	int error = mkdirat(logDir, path, S_IRWXU);
+	if (error && errno != EEXIST) err(EX_CANTCREAT, "log/%s", path);
 }
 
-static void sanitize(char *name) {
-	for (; name[0]; ++name) {
-		if (name[0] == '/') name[0] = '_';
+static void sanitize(char *ptr, char *end) {
+	for (char *ch = ptr; ch < end && *ch == '.'; ++ch) {
+		*ch = '_';
+	}
+	for (char *ch = ptr; ch < end; ++ch) {
+		if (*ch == '/') *ch = '_';
 	}
 }
 
-static FILE *logFile(struct Tag tag, const struct tm *time) {
-	struct Log *log = &logs[tag.id];
-	if (
-		log->file
-		&& log->year == time->tm_year
-		&& log->month == time->tm_mon
-		&& log->day == time->tm_mday
-	) return log->file;
+static struct {
+	int year;
+	int month;
+	int day;
+	FILE *file;
+} logs[IDCap];
 
-	if (log->file) {
-		fclose(log->file);
+static FILE *logFile(uint id, const struct tm *tm) {
+	if (
+		logs[id].file &&
+		logs[id].year == tm->tm_year &&
+		logs[id].month == tm->tm_mon &&
+		logs[id].day == tm->tm_mday
+	) return logs[id].file;
+
+	if (logs[id].file) {
+		int error = fclose(logs[id].file);
+		if (error) err(EX_IOERR, "%s", idNames[id]);
+	}
 
-	} else {
-		char *name = strdup(tag.name);
-		if (!name) err(EX_OSERR, "strdup");
-		sanitize(name);
+	logs[id].year = tm->tm_year;
+	logs[id].month = tm->tm_mon;
+	logs[id].day = tm->tm_mday;
 
-		int error = mkdirat(logRoot, name, 0700);
-		if (error && errno != EEXIST) err(EX_CANTCREAT, "%s", name);
+	char path[PATH_MAX];
+	char *ptr = path, *end = &path[sizeof(path)];
 
-		log->dir = openat(logRoot, name, O_RDONLY | O_CLOEXEC);
-		if (log->dir < 0) err(EX_CANTCREAT, "%s", name);
+	ptr = seprintf(ptr, end, "%s", network.name);
+	sanitize(path, ptr);
+	logMkdir(path);
 
-		free(name);
-	}
+	char *name = ptr;
+	ptr = seprintf(ptr, end, "/%s", idNames[id]);
+	sanitize(&name[1], ptr);
+	logMkdir(path);
 
-	log->year = time->tm_year;
-	log->month = time->tm_mon;
-	log->day = time->tm_mday;
+	size_t len = strftime(ptr, end - ptr, "/%F.log", tm);
+	if (!len) errx(EX_CANTCREAT, "log path too long");
 
-	char path[sizeof("YYYY-MM-DD.log")];
-	strftime(path, sizeof(path), "%F.log", time);
 	int fd = openat(
-		log->dir, path, O_RDWR | O_APPEND | O_CREAT | O_CLOEXEC, 0600
+		logDir, path,
+		O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC,
+		S_IRUSR | S_IWUSR
 	);
-	if (fd < 0) err(EX_CANTCREAT, "%s/%s", tag.name, path);
+	if (fd < 0) err(EX_CANTCREAT, "log/%s", path);
+	logs[id].file = fdopen(fd, "a");
+	if (!logs[id].file) err(EX_OSERR, "fdopen");
 
-	log->file = fdopen(fd, "a+");
-	if (!log->file) err(EX_CANTCREAT, "%s/%s", tag.name, path);
-	setlinebuf(log->file);
-
-	return log->file;
+	setlinebuf(logs[id].file);
+	return logs[id].file;
 }
 
-enum { StampLen = sizeof("YYYY-MM-DDThh:mm:ss+hhmm") - 1 };
-
-void logFmt(struct Tag tag, const time_t *ts, const char *format, ...) {
-	if (logRoot < 0) return;
-
-	time_t t;
-	if (!ts) {
-		t = time(NULL);
-		ts = &t;
+void logClose(void) {
+	if (logDir < 0) return;
+	for (uint id = 0; id < IDCap; ++id) {
+		if (!logs[id].file) continue;
+		int error = fclose(logs[id].file);
+		if (error) err(EX_IOERR, "%s", idNames[id]);
 	}
+	close(logDir);
+}
 
-	struct tm *time = localtime(ts);
-	if (!time) err(EX_SOFTWARE, "localtime");
+void logFormat(uint id, const time_t *src, const char *format, ...) {
+	if (logDir < 0) return;
 
-	FILE *file = logFile(tag, time);
+	time_t ts = (src ? *src : time(NULL));
+	struct tm *tm = localtime(&ts);
+	if (!tm) err(EX_OSERR, "localtime");
 
-	char stamp[StampLen + 1];
-	strftime(stamp, sizeof(stamp), "%FT%T%z", time);
-	fprintf(file, "[%s] ", stamp);
-	if (ferror(file)) err(EX_IOERR, "%s", tag.name);
+	FILE *file = logFile(id, tm);
+
+	char buf[sizeof("0000-00-00T00:00:00+0000")];
+	strftime(buf, sizeof(buf), "%FT%T%z", tm);
+	int n = fprintf(file, "[%s] ", buf);
+	if (n < 0) err(EX_IOERR, "%s", idNames[id]);
 
 	va_list ap;
 	va_start(ap, format);
-	vfprintf(file, format, ap);
+	n = vfprintf(file, format, ap);
 	va_end(ap);
-	if (ferror(file)) err(EX_IOERR, "%s", tag.name);
-
-	fprintf(file, "\n");
-	if (ferror(file)) err(EX_IOERR, "%s", tag.name);
-}
-
-static void logRead(struct Tag tag, bool replay) {
-	if (logRoot < 0) return;
-
-	time_t t = time(NULL);
-	struct tm *time = localtime(&t);
-	if (!time) err(EX_SOFTWARE, "localtime");
-
-	FILE *file = logFile(tag, time);
-	rewind(file);
-
-	char *line = NULL;
-	size_t cap = 0;
-	ssize_t len;
-	while (0 < (len = getline(&line, &cap, file))) {
-		if (replay) {
-			if (len < 1 + StampLen + 2 + 1) continue;
-			line[len - 1] = '\0';
-			uiFmt(tag, UICold, "\3%d%s", IRCGray, &line[1 + StampLen + 2]);
-		} else {
-			printf("%s", line);
-		}
-	}
-	if (ferror(file)) err(EX_IOERR, "%s", tag.name);
-	free(line);
-}
-
-void logList(struct Tag tag) {
-	logRead(tag, false);
-}
+	if (n < 0) err(EX_IOERR, "%s", idNames[id]);
 
-void logReplay(struct Tag tag) {
-	logRead(tag, true);
+	n = fprintf(file, "\n");
+	if (n < 0) err(EX_IOERR, "%s", idNames[id]);
 }