diff options
| -rw-r--r-- | Makefile | 13 | ||||
| -rw-r--r-- | chat.c | 3 | ||||
| -rw-r--r-- | chat.h | 5 | ||||
| -rw-r--r-- | handle.c | 18 | ||||
| -rw-r--r-- | log.c | 121 | 
5 files changed, 157 insertions, 3 deletions
| @@ -7,7 +7,18 @@ CFLAGS += $(LIBRESSL_PREFIX:%=-I%/include)  LDFLAGS += $(LIBRESSL_PREFIX:%=-L%/lib)  LDLIBS = -lcursesw -ltls -OBJS = chat.o edit.o handle.o input.o irc.o pls.o tab.o tag.o term.o ui.o url.o +OBJS += chat.o +OBJS += edit.o +OBJS += handle.o +OBJS += input.o +OBJS += irc.o +OBJS += log.o +OBJS += pls.o +OBJS += tab.o +OBJS += tag.o +OBJS += term.o +OBJS += ui.o +OBJS += url.o  all: tags chat @@ -161,11 +161,12 @@ int main(int argc, char *argv[]) {  	const char *webirc = NULL;  	int opt; -	while (0 < (opt = getopt(argc, argv, "W:h:j:n:p:u:vw:"))) { +	while (0 < (opt = getopt(argc, argv, "W:h:j:l:n:p:u:vw:"))) {  		switch (opt) {  			break; case 'W': webirc = optarg;  			break; case 'h': host = strdup(optarg);  			break; case 'j': selfJoin(optarg); +			break; case 'l': logOpen(optarg);  			break; case 'n': selfNick(optarg);  			break; case 'p': port = optarg;  			break; case 'u': selfUser(optarg); @@ -154,6 +154,11 @@ void urlScan(struct Tag tag, const char *str);  void urlList(struct Tag tag);  void urlOpen(struct Tag tag, size_t at, size_t to); +void logOpen(const char *path); +void logFmt( +	struct Tag tag, const time_t *ts, const char *format, ... +) __attribute__((format(printf, 3, 4))); +  void spawn(char *const argv[]);  wchar_t *ambstowcs(const char *src); @@ -166,6 +166,7 @@ static void handleJoin(char *prefix, char *params) {  		"\3%d%s\3 arrives in \3%d%s\3",  		color(user), nick, color(chan), chan  	); +	logFmt(tag, NULL, "%s arrives in %s", nick, chan);  }  static void handlePart(char *prefix, char *params) { @@ -184,14 +185,16 @@ static void handlePart(char *prefix, char *params) {  		uiFmt(  			tag, UI_COLD,  			"\3%d%s\3 leaves \3%d%s\3, \"%s\"", -			color(user), nick, color(chan), chan, mesg +			color(user), nick, color(chan), chan, dequote(mesg)  		); +		logFmt(tag, NULL, "%s leaves %s, \"%s\"", nick, chan, dequote(mesg));  	} else {  		uiFmt(  			tag, UI_COLD,  			"\3%d%s\3 leaves \3%d%s\3",  			color(user), nick, color(chan), chan  		); +		logFmt(tag, NULL, "%s leaves %s", nick, chan);  	}  } @@ -215,12 +218,17 @@ static void handleKick(char *prefix, char *params) {  			color(user), nick, color(kick), kick, color(chan), chan,  			dequote(mesg)  		); +		logFmt( +			tag, NULL, +			"%s kicks %s out of %s, \"%s\"", nick, kick, chan, dequote(mesg) +		);  	} else {  		uiFmt(  			tag, (kicked ? UI_HOT : UI_COLD),  			"\3%d%s\3 kicks \3%d%s\3 out of \3%d%s\3",  			color(user), nick, color(kick), kick, color(chan), chan  		); +		logFmt(tag, NULL, "%s kicks %s out of %s", nick, kick, chan);  	}  } @@ -239,8 +247,10 @@ static void handleQuit(char *prefix, char *params) {  				"\3%d%s\3 leaves, \"%s\"",  				color(user), nick, dequote(mesg)  			); +			logFmt(tag, NULL, "%s leaves, \"%s\"", nick, dequote(mesg));  		} else {  			uiFmt(tag, UI_COLD, "\3%d%s\3 leaves", color(user), nick); +			logFmt(tag, NULL, "%s leaves", nick);  		}  	}  } @@ -257,6 +267,7 @@ static void handleReplyTopic(char *prefix, char *params) {  		"The sign in \3%d%s\3 reads, \"%s\"",  		color(chan), chan, topic  	); +	logFmt(tag, NULL, "The sign in %s reads, \"%s\"", chan, topic);  }  static void handleTopic(char *prefix, char *params) { @@ -273,6 +284,7 @@ static void handleTopic(char *prefix, char *params) {  		"\3%d%s\3 places a new sign in \3%d%s\3, \"%s\"",  		color(user), nick, color(chan), chan, topic  	); +	logFmt(tag, NULL, "%s places a new sign in %s, \"%s\"", nick, chan, topic);  }  static void handleReplyEndOfNames(char *prefix, char *params) { @@ -335,6 +347,7 @@ static void handleNick(char *prefix, char *params) {  			"\3%d%s\3 is now known as \3%d%s\3",  			color(user), prev, color(user), next  		); +		logFmt(tag, NULL, "%s is now known as %s", prev, next);  	}  } @@ -354,6 +367,7 @@ static void handleCTCP(struct Tag tag, char *nick, char *user, char *mesg) {  		"%c\3%d* %s\17 %s",  		ping["\17\26"], color(user), nick, params  	); +	logFmt(tag, NULL, "* %s %s", nick, params);  }  static void handlePrivmsg(char *prefix, char *params) { @@ -375,6 +389,7 @@ static void handlePrivmsg(char *prefix, char *params) {  		"%c\3%d%c%s%c\17 %s",  		ping["\17\26"], color(user), self["<("], nick, self[">)"], mesg  	); +	logFmt(tag, NULL, "<%s> %s", nick, mesg);  }  static void handleNotice(char *prefix, char *params) { @@ -393,6 +408,7 @@ static void handleNotice(char *prefix, char *params) {  		"%c\3%d-%s-\17 %s",  		ping["\17\26"], color(user), nick, mesg  	); +	logFmt(tag, NULL, "-%s- %s", nick, mesg);  }  static const struct { @@ -0,0 +1,121 @@ +/* Copyright (C) 2018  Curtis 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 + * 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. + * + * 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/>. + */ + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sysexits.h> +#include <time.h> + +#include "chat.h" + +static int logRoot = -1; + +static struct Log { +	int dir; +	int year; +	int month; +	int day; +	FILE *file; +} logs[TAGS_LEN]; + +void logOpen(const char *path) { +	logRoot = open(path, O_RDONLY | O_CLOEXEC); +	if (logRoot < 0) err(EX_CANTCREAT, "%s", path); +} + +static void sanitize(char *name) { +	for (; name[0]; ++name) { +		if (name[0] == '/') name[0] = '_'; +	} +} + +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; + +	if (log->file) { +		fclose(log->file); + +	} else { +		char *name = strdup(tag.name); +		if (!name) err(EX_OSERR, "strdup"); +		sanitize(name); + +		int error = mkdirat(logRoot, name, 0700); +		if (error && errno != EEXIST) err(EX_CANTCREAT, "%s", name); + +		log->dir = openat(logRoot, name, O_RDONLY | O_CLOEXEC); +		if (log->dir < 0) err(EX_CANTCREAT, "%s", name); + +		free(name); +	} + +	log->year = time->tm_year; +	log->month = time->tm_mon; +	log->day = time->tm_mday; + +	char path[sizeof("YYYY-MM-DD.log")]; +	strftime(path, sizeof(path), "%F.log", time); +	int fd = openat( +		log->dir, path, O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC, 0700 +	); +	if (fd < 0) err(EX_CANTCREAT, "%s/%s", tag.name, path); + +	log->file = fdopen(fd, "a"); +	if (!log->file) err(EX_CANTCREAT, "%s/%s", tag.name, path); +	setlinebuf(log->file); + +	return log->file; +} + +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; +	} + +	struct tm *time = localtime(ts); +	if (!time) err(EX_SOFTWARE, "localtime"); + +	FILE *file = logFile(tag, time); + +	char stamp[sizeof("YYYY-MM-DDThh:mm:ss+hhmm")]; +	strftime(stamp, sizeof(stamp), "%FT%T%z", time); +	fprintf(file, "[%s] ", stamp); +	if (ferror(file)) err(EX_IOERR, "%s", tag.name); + +	va_list ap; +	va_start(ap, format); +	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); +} | 
