summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--bounce.c27
-rw-r--r--bounce.h3
-rw-r--r--pounce.110
-rw-r--r--ring.c78
4 files changed, 117 insertions, 1 deletions
diff --git a/bounce.c b/bounce.c
index 65789ab..4e7458c 100644
--- a/bounce.c
+++ b/bounce.c
@@ -24,6 +24,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/socket.h>
+#include <sys/stat.h>
 #include <sysexits.h>
 #include <tls.h>
 #include <unistd.h>
@@ -88,11 +89,20 @@ static char *sensitive(char *arg) {
 	return value;
 }
 
+static FILE *saveFile;
+static void exitSave(void) {
+	int error = ringSave(saveFile);
+	if (error) warn("fwrite");
+	error = fclose(saveFile);
+	if (error) warn("fclose");
+}
+
 int main(int argc, char *argv[]) {
 	const char *localHost = "localhost";
 	const char *localPort = "6697";
 	char certPath[PATH_MAX] = "";
 	char privPath[PATH_MAX] = "";
+	const char *save = NULL;
 
 	bool insecure = false;
 	const char *host = NULL;
@@ -107,7 +117,8 @@ int main(int argc, char *argv[]) {
 	const char *quit = "connection reset by purr";
 
 	int opt;
-	while (0 < (opt = getopt(argc, argv, "!A:C:H:K:NP:Q:W:a:h:j:n:p:r:u:vw:"))) {
+	const char *opts = "!A:C:H:K:NP:Q:W:a:f:h:j:n:p:r:u:vw:";
+	while (0 < (opt = getopt(argc, argv, opts))) {
 		switch (opt) {
 			break; case '!': insecure = true;
 			break; case 'A': away = optarg;
@@ -119,6 +130,7 @@ int main(int argc, char *argv[]) {
 			break; case 'Q': quit = optarg;
 			break; case 'W': clientPass = sensitive(optarg);
 			break; case 'a': auth = sensitive(optarg);
+			break; case 'f': save = optarg;
 			break; case 'h': host = optarg;
 			break; case 'j': join = optarg;
 			break; case 'n': nick = optarg;
@@ -146,6 +158,19 @@ int main(int argc, char *argv[]) {
 	if (!user) user = nick;
 	if (!real) real = nick;
 
+	if (save) {
+		umask(0066);
+		saveFile = fopen(save, "a+");
+		if (!saveFile) err(EX_CANTCREAT, "%s", save);
+
+		rewind(saveFile);
+		ringLoad(saveFile);
+
+		int error = ftruncate(fileno(saveFile), 0);
+		if (error) err(EX_IOERR, "ftruncate");
+		atexit(exitSave);
+	}
+
 	listenConfig(certPath, privPath);
 
 	int bind[8];
diff --git a/bounce.h b/bounce.h
index 9221185..6e9ddbd 100644
--- a/bounce.h
+++ b/bounce.h
@@ -15,6 +15,7 @@
  */
 
 #include <stdbool.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
@@ -64,6 +65,8 @@ size_t ringDiff(size_t consumer);
 const char *ringPeek(time_t *time, size_t consumer);
 const char *ringConsume(time_t *time, size_t consumer);
 void ringInfo(void);
+int ringSave(FILE *file);
+void ringLoad(FILE *file);
 
 void listenConfig(const char *cert, const char *priv);
 size_t listenBind(int fds[], size_t cap, const char *host, const char *port);
diff --git a/pounce.1 b/pounce.1
index 41555ed..632ca3e 100644
--- a/pounce.1
+++ b/pounce.1
@@ -17,6 +17,7 @@
 .Op Fl Q Ar quit
 .Op Fl W Ar pass
 .Op Fl a Ar auth
+.Op Fl f Ar file
 .Op Fl h Ar host
 .Op Fl j Ar chan
 .Op Fl n Ar nick
@@ -108,6 +109,15 @@ Set
 to the first line read from
 .Ar file .
 .
+.It Fl f Ar file
+Load the contents of the ring buffer from
+.Ar file
+if it exists.
+The file is then truncated.
+On shutdown,
+save the contents of the ring buffer to
+.Ar file .
+.
 .It Fl h Ar host
 Connect to
 .Ar host .
diff --git a/ring.c b/ring.c
index 49fdd9b..474a6e4 100644
--- a/ring.c
+++ b/ring.c
@@ -103,3 +103,81 @@ void ringInfo(void) {
 		);
 	}
 }
+
+static const size_t FileVersion = 0x0165636E756F70;
+
+static int writeSize(FILE *file, size_t value) {
+	return (fwrite(&value, sizeof(value), 1, file) ? 0 : -1);
+}
+static int writeTime(FILE *file, time_t time) {
+	return (fwrite(&time, sizeof(time), 1, file) ? 0 : -1);
+}
+static int writeString(FILE *file, const char *str) {
+	return (fwrite(str, strlen(str) + 1, 1, file) ? 0 : -1);
+}
+
+int ringSave(FILE *file) {
+	if (writeSize(file, FileVersion)) return -1;
+	if (writeSize(file, producer)) return -1;
+	if (writeSize(file, consumers.len)) return -1;
+	for (size_t i = 0; i < consumers.len; ++i) {
+		if (writeString(file, consumers.ptr[i].name)) return -1;
+		if (writeSize(file, consumers.ptr[i].pos)) return -1;
+	}
+	for (size_t i = 0; i < RingLen; ++i) {
+		if (writeTime(file, ring.times[i])) return -1;
+	}
+	for (size_t i = 0; i < RingLen; ++i) {
+		if (!ring.lines[i]) break;
+		if (writeString(file, ring.lines[i])) return -1;
+	}
+	return 0;
+}
+
+static void readSize(FILE *file, size_t *value) {
+	fread(value, sizeof(*value), 1, file);
+	if (ferror(file)) err(EX_IOERR, "fread");
+	if (feof(file)) err(EX_DATAERR, "unexpected eof");
+}
+static void readTime(FILE *file, time_t *time) {
+	fread(time, sizeof(*time), 1, file);
+	if (ferror(file)) err(EX_IOERR, "fread");
+	if (feof(file)) err(EX_DATAERR, "unexpected eof");
+}
+static void readString(FILE *file, char **buf, size_t *cap) {
+	ssize_t len = getdelim(buf, cap, '\0', file);
+	if (len < 0 && !feof(file)) err(EX_IOERR, "getdelim");
+}
+
+void ringLoad(FILE *file) {
+	size_t version;
+	fread(&version, sizeof(version), 1, file);
+	if (ferror(file)) err(EX_IOERR, "fread");
+	if (feof(file)) return;
+
+	if (version != FileVersion) errx(EX_DATAERR, "unknown file version");
+	readSize(file, &producer);
+
+	char *buf = NULL;
+	size_t cap = 0;
+
+	size_t len;
+	readSize(file, &len);
+	for (size_t i = 0; i < len; ++i) {
+		readString(file, &buf, &cap);
+		size_t consumer = ringConsumer(buf);
+		readSize(file, &consumers.ptr[consumer].pos);
+	}
+
+	for (size_t i = 0; i < RingLen; ++i) {
+		readTime(file, &ring.times[i]);
+	}
+	for (size_t i = 0; i < RingLen; ++i) {
+		readString(file, &buf, &cap);
+		if (feof(file)) break;
+		ring.lines[i] = strdup(buf);
+		if (!ring.lines[i]) err(EX_OSERR, "strdup");
+	}
+
+	free(buf);
+}