diff options
-rw-r--r-- | README.7 | 8 | ||||
-rw-r--r-- | config.c | 2 | ||||
-rw-r--r-- | database.h | 17 | ||||
-rw-r--r-- | litterbox.1 | 34 | ||||
-rw-r--r-- | litterbox.c | 55 | ||||
-rw-r--r-- | scoop.1 | 2 | ||||
-rw-r--r-- | scoop.c | 4 | ||||
-rw-r--r-- | unscoop.1 | 34 | ||||
-rw-r--r-- | unscoop.c | 4 | ||||
-rw-r--r-- | xdg.c | 91 |
10 files changed, 128 insertions, 123 deletions
diff --git a/README.7 b/README.7 index ca99c11..a33b34c 100644 --- a/README.7 +++ b/README.7 @@ -1,4 +1,4 @@ -.Dd May 18, 2021 +.Dd October 21, 2023 .Dt README 7 .Os "Causal Agency" . @@ -76,13 +76,11 @@ PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./configure . .Pp On -.Fx -and .Ox the recommended way to run .Nm is with the process supervisor -.Lk https://git.causal.agency/catsit catsit . +.Lk https://git.causal.agency/kitd kitd . . .Sh FILES .Bl -tag -width "litterbox.c" -compact @@ -127,7 +125,7 @@ Web interface: .Lk https://git.causal.agency/scooper scooper .It .Rs -.%A June Bug +.%A June McEnroe .%T IRC Suite .%U https://text.causal.agency/010-irc-suite.txt .%D June 19, 2020 diff --git a/config.c b/config.c index 861bf88..3448e0d 100644 --- a/config.c +++ b/config.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 C. McEnroe <june@causal.agency> +/* Copyright (C) 2019 June McEnroe <june@causal.agency> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/database.h b/database.h index 15704f0..a6db904 100644 --- a/database.h +++ b/database.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 C. McEnroe <june@causal.agency> +/* Copyright (C) 2019 June McEnroe <june@causal.agency> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -41,11 +41,10 @@ #define SQL(...) #__VA_ARGS__ #define ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0])) -const char *configPath(const char **dirs, const char *path); -const char *dataPath(const char **dirs, const char *path); +char *configPath(char *buf, size_t cap, const char *path, int i); +char *dataPath(char *buf, size_t cap, const char *path, int i); FILE *configOpen(const char *path, const char *mode); FILE *dataOpen(const char *path, const char *mode); -void dataMkdir(const char *path); int getopt_config( int argc, char *const *argv, const char *optstring, const struct option *longopts, int *longindex @@ -114,13 +113,14 @@ static inline void dbFind(const char *path, int flags) { errx(EX_NOINPUT, "%s: database not found", path); } + char buf[PATH_MAX]; if (flags & SQLITE_OPEN_CREATE) { - dataMkdir(""); + int error = mkdir(dataPath(buf, sizeof(buf), "", 0), S_IRWXU); + if (error && errno != EEXIST) err(EX_CANTCREAT, "%s", buf); } - const char *dirs = NULL; - while (NULL != (path = dataPath(&dirs, DatabasePath))) { - dbOpen(path, flags); + for (int i = 0; dataPath(buf, sizeof(buf), DatabasePath, i); ++i) { + dbOpen(buf, flags); if (db) return; } errx(EX_NOINPUT, "database not found; initialize it with litterbox -i"); @@ -152,7 +152,6 @@ static inline void dbClose(void) { free(persist); persist = prev; } - dbExec(SQL(PRAGMA optimize;)); sqlite3_close(db); } diff --git a/litterbox.1 b/litterbox.1 index 47f2839..c54a576 100644 --- a/litterbox.1 +++ b/litterbox.1 @@ -94,13 +94,13 @@ following their corresponding flags. The arguments are as follows: . .Bl -tag -width "-h host" -.It Fl N Ar name , Cm network = Ar name +.It Fl N Ar name | Cm network = Ar name Set the network name to be used if the server does not send .Sy RPL_ISUPPORT NETWORK . The default is the server hostname. . -.It Fl Q , Cm public-query +.It Fl Q | Cm public-query Enable the public search query interface. This allows anyone to perform searches in private messages to @@ -117,7 +117,7 @@ The searchable columns are .Li target , .Li message . . -.It Fl U Ar url , Cm scooper-url = Ar url +.It Fl U Ar url | Cm scooper-url = Ar url Set the base URL of a .Xr scooper 1 instance @@ -133,7 +133,7 @@ Perform a live database backup to and exit. This operation requires SQLite version 3.27.0 or newer. . -.It Fl c Ar path , Cm cert = Ar path +.It Fl c Ar path | Cm cert = Ar path Load the TLS client certificate from .Ar path and authenticate with SASL EXTERNAL, @@ -146,24 +146,24 @@ If the private key is in a separate file, it is loaded with .Fl k . . -.It Fl d Ar path , Cm database = Ar path +.It Fl d Ar path | Cm database = Ar path Set the path to the database file. See .Sx FILES for the default paths. . -.It Fl h Ar host , Cm host = Ar host +.It Fl h Ar host | Cm host = Ar host Connect to .Ar host . . .It Fl i Initialize the database and exit. . -.It Fl j Ar chan , Cm join = Ar chan +.It Fl j Ar chan | Cm join = Ar chan Join the comma-separated list of channels .Ar chan . . -.It Fl k Ar path , Cm priv = Ar path +.It Fl k Ar path | Cm priv = Ar path Load the TLS client private key from .Ar path . The @@ -171,7 +171,7 @@ The is searched for in the same manner as configuration files. . -.It Fl l Ar limit , Cm limit = Ar limit +.It Fl l Ar limit | Cm limit = Ar limit Limit the number of results in the search query interface enabled by @@ -184,18 +184,18 @@ The default limit is 10. Migrate the database to the latest format and exit. . -.It Fl n Ar nick , Cm nick = Ar nick +.It Fl n Ar nick | Cm nick = Ar nick Set the nickname to .Ar nick . The default nickname is .Dq litterbox . . -.It Fl p Ar port , Cm port = Ar port +.It Fl p Ar port | Cm port = Ar port Connect to .Ar port . The default port is 6697. . -.It Fl q , Cm private-query +.It Fl q | Cm private-query Enable the private search query interface. When connected to .Xr pounce 1 , @@ -214,7 +214,7 @@ The searchable columns are .Li target , .Li message . . -.It Fl t Ar path , Cm trust = Ar path +.It Fl t Ar path | Cm trust = Ar path Trust the self-signed certificate loaded from .Ar path and disable server name verification. @@ -223,17 +223,17 @@ The is searched for in the same manner as configuration files. . -.It Fl u Ar user , Cm user = Ar user +.It Fl u Ar user | Cm user = Ar user Set the username to .Ar user . The default username is the same as the nickname. . -.It Fl v , Cm verbose +.It Fl v | Cm verbose Write sent and received IRC messages as well as SQL INSERT statements to standard error. . -.It Fl w Ar pass , Cm pass = Ar pass +.It Fl w Ar pass | Cm pass = Ar pass Log in with the server password .Ar pass . .El @@ -335,7 +335,7 @@ offered by .Xr pounce 1 . . .Sh AUTHORS -.An June Bug Aq Mt june@causal.agency +.An June McEnroe Aq Mt june@causal.agency . .Sh BUGS Send mail to diff --git a/litterbox.c b/litterbox.c index 48e5b60..f33ed22 100644 --- a/litterbox.c +++ b/litterbox.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 C. McEnroe <june@causal.agency> +/* Copyright (C) 2019 June McEnroe <june@causal.agency> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -432,6 +432,23 @@ static void insertEvent( dbRun(stmt); } +static enum Type messageType(struct Message *msg) { + if (msg->cmd[0] == 'N') return Notice; + if (strncmp(msg->params[1], "\1ACTION", 7)) return Privmsg; + if (msg->params[1][7] == ' ') { + msg->params[1] += 8; + } else if (msg->params[1][7] == '\1') { + msg->params[1] += 7; + } else { + return Privmsg; + } + size_t len = strlen(msg->params[1]); + if (msg->params[1][len - 1] == '\1') { + msg->params[1][len - 1] = '\0'; + } + return Action; +} + static void handlePrivmsg(struct Message *msg) { require(msg, true, 2); @@ -440,14 +457,7 @@ static void handlePrivmsg(struct Message *msg) { if (statusmsg) context += strspn(context, statusmsg); if (strchr(chanTypes, context[0])) query = false; if (!strcmp(context, self)) context = msg->nick; - - enum Type type = (!strcmp(msg->cmd, "NOTICE") ? Notice : Privmsg); - char *message = msg->params[1]; - if (!strncmp(message, "\1ACTION ", 8)) { - message += 8; - message[strcspn(message, "\1")] = '\0'; - type = Action; - } + enum Type type = messageType(msg); bool selfMessage = !strcmp(msg->nick, msg->params[0]); if (query && searchQuery && type == Privmsg) { @@ -460,7 +470,7 @@ static void handlePrivmsg(struct Message *msg) { insertContext(context, query); insertName(msg); - insertEvent(msg, type, context, NULL, message); + insertEvent(msg, type, context, NULL, msg->params[1]); } static void insertTopic( @@ -742,8 +752,9 @@ static void handle(struct Message *msg) { } static void atExit(void) { - if (client) tls_close(client); + dbExec(SQL(PRAGMA optimize;)); dbClose(); + if (client) tls_close(client); } static void quit(int sig) { @@ -756,7 +767,7 @@ static void quit(int sig) { int main(int argc, char *argv[]) { bool init = false; bool migrate = false; - const char *path = NULL; + const char *dbPath = NULL; const char *backup = NULL; bool insecure = false; @@ -806,7 +817,7 @@ int main(int argc, char *argv[]) { break; case 'U': scooperURL = optarg; break; case 'b': backup = optarg; break; case 'c': cert = optarg; - break; case 'd': path = optarg; + break; case 'd': dbPath = optarg; break; case 'h': host = optarg; break; case 'i': init = true; break; case 'j': join = optarg; @@ -831,7 +842,7 @@ int main(int argc, char *argv[]) { int flags = SQLITE_OPEN_READWRITE; if (init) flags |= SQLITE_OPEN_CREATE; - dbFind(path, flags); + dbFind(dbPath, flags); atexit(atExit); if (init) { @@ -859,22 +870,18 @@ int main(int argc, char *argv[]) { client = tls_client(); if (!client) errx(EX_SOFTWARE, "tls_client"); + int error; + char path[PATH_MAX]; struct tls_config *config = tls_config_new(); if (!config) errx(EX_SOFTWARE, "tls_config_new"); - int error = tls_config_set_ciphers(config, "compat"); - if (error) { - errx(EX_SOFTWARE, "tls_config_set_ciphers: %s", tls_config_error(config)); - } - if (insecure) { tls_config_insecure_noverifycert(config); tls_config_insecure_noverifyname(config); } if (trust) { tls_config_insecure_noverifyname(config); - const char *dirs = NULL; - while (NULL != (path = configPath(&dirs, trust))) { + for (int i = 0; configPath(path, sizeof(path), trust, i); ++i) { error = tls_config_set_ca_file(config, path); if (!error) break; } @@ -882,8 +889,7 @@ int main(int argc, char *argv[]) { } if (cert) { - const char *dirs = NULL; - while (NULL != (path = configPath(&dirs, cert))) { + for (int i = 0; configPath(path, sizeof(path), cert, i); ++i) { if (priv) { error = tls_config_set_cert_file(config, path); } else { @@ -894,8 +900,7 @@ int main(int argc, char *argv[]) { if (error) errx(EX_NOINPUT, "%s: %s", cert, tls_config_error(config)); } if (priv) { - const char *dirs = NULL; - while (NULL != (path = configPath(&dirs, priv))) { + for (int i = 0; configPath(path, sizeof(path), priv, i); ++i) { error = tls_config_set_key_file(config, path); if (!error) break; } diff --git a/scoop.1 b/scoop.1 index 62e0ba2..8687e5d 100644 --- a/scoop.1 +++ b/scoop.1 @@ -245,7 +245,7 @@ The default is .El . .Sh AUTHORS -.An June Bug Aq Mt june@causal.agency +.An June McEnroe Aq Mt june@causal.agency . .Sh BUGS Send mail to diff --git a/scoop.c b/scoop.c index 031e4e2..73aeb89 100644 --- a/scoop.c +++ b/scoop.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 C. McEnroe <june@causal.agency> +/* Copyright (C) 2019 June McEnroe <june@causal.agency> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -529,7 +529,7 @@ int main(int argc, char *argv[]) { binds[n++] = Bind(":limit", limit, 0); } - dbFind(path, SQLITE_OPEN_READWRITE); + dbFind(path, SQLITE_OPEN_READONLY); if (dbVersion() != DatabaseVersion) { errx(EX_CONFIG, "database out of date; migrate with litterbox -m"); } diff --git a/unscoop.1 b/unscoop.1 index ac089b6..ddb0fb5 100644 --- a/unscoop.1 +++ b/unscoop.1 @@ -64,7 +64,21 @@ which must have been initialized by The default path is as in .Xr litterbox 1 . . -.It Fl f Cm catgirl +.It Fl f Ar format +Set the input log format. +The default is +.Cm generic . +. +.It Fl v +Print SQL +.Sy INSERT +statements on standard error. +.El +. +.Pp +The formats are as follows: +.Bl -tag -width Ds +.It Cm catgirl Import logs from the .Xr catgirl 1 IRC client. @@ -74,9 +88,8 @@ find ~/.local/share/catgirl/log \e xargs -0 unscoop -f catgirl .Ed . -.It Fl f Cm generic +.It Cm generic Import logs using generic matchers. -This is the default. Network and context names are inferred from paths of the form .Pa network/context/* . @@ -87,7 +100,7 @@ Events of the following formats are matched: [timestamp] * nick action .Ed . -.It Fl f Cm irc +.It Cm irc Import logs formatted as IRC protocol messages tagged with .Sy server-time . @@ -96,7 +109,7 @@ The network and context must be set with and .Fl c . . -.It Fl f Cm textual +.It Cm textual Import logs from the Textual IRC client. .Bd -literal -offset indent find Textual -type f -name '*.txt' \e @@ -104,7 +117,7 @@ find Textual -type f -name '*.txt' \e xargs -0 unscoop -f textual .Ed . -.It Fl f Cm weechat +.It Cm weechat Import logs from the WeeChat IRC client. .Bd -literal -offset indent find ~/.weechat/logs -type f -name 'irc.*.weechatlog' \e @@ -121,7 +134,7 @@ Import these logs explicitly with and .Fl c . . -.It Fl f Cm znc +.It Cm znc Import logs from the .Xr znc 1 .Sy log @@ -134,18 +147,13 @@ find ~/.znc/moddata/log \e -not -path '*/status/*' -print0 | xargs -0 unscoop -f znc .Ed -. -.It Fl v -Print SQL -.Sy INSERT -statements on standard error. .El . .Sh SEE ALSO .Xr litterbox 1 . .Sh AUTHORS -.An June Bug Aq Mt june@causal.agency +.An June McEnroe Aq Mt june@causal.agency . .Sh BUGS Send mail to diff --git a/unscoop.c b/unscoop.c index 35e25a9..a290831 100644 --- a/unscoop.c +++ b/unscoop.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 C. McEnroe <june@causal.agency> +/* Copyright (C) 2019 June McEnroe <june@causal.agency> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -533,6 +533,8 @@ int main(int argc, char *argv[]) { } for (ssize_t len; 0 < (len = getline(&line, &cap, file));) { + if (len >= 1 && line[len-1] == '\n') line[len-1] = '\0'; + if (len >= 2 && line[len-2] == '\r') line[len-2] = '\0'; matchLine(format, regex, line); sizeRead += len; if (100 * sizeRead / sizeTotal != sizePercent) { diff --git a/xdg.c b/xdg.c index c22bc0a..e5d9232 100644 --- a/xdg.c +++ b/xdg.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2019, 2020 C. McEnroe <june@causal.agency> +/* Copyright (C) 2019, 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 General Public License as published by @@ -59,80 +59,73 @@ static const struct Base Data = { .defDirs = "/usr/local/share:/usr/share", }; -static const char * -basePath(struct Base base, const char **dirs, const char *path) { - static char buf[PATH_MAX]; - - if (*dirs) { - if (!**dirs) return NULL; - size_t len = strcspn(*dirs, ":"); - snprintf(buf, sizeof(buf), "%.*s/" SUBDIR "/%s", (int)len, *dirs, path); - *dirs += len; - if (**dirs) *dirs += 1; +static char *basePath( + struct Base base, char *buf, size_t cap, const char *path, int i +) { + if (path[strspn(path, ".")] == '/') { + if (i > 0) return NULL; + snprintf(buf, cap, "%s", path); return buf; } - if (path[strspn(path, ".")] == '/') { - *dirs = ""; - return path; + if (i > 0) { + const char *dirs = getenv(base.envDirs); + if (!dirs) dirs = base.defDirs; + for (; i > 1; --i) { + dirs += strcspn(dirs, ":"); + dirs += (*dirs == ':'); + } + if (!*dirs) return NULL; + snprintf( + buf, cap, "%.*s/" SUBDIR "/%s", + (int)strcspn(dirs, ":"), dirs, path + ); + return buf; } - *dirs = getenv(base.envDirs); - if (!*dirs) *dirs = base.defDirs; - const char *home = getenv("HOME"); const char *baseHome = getenv(base.envHome); if (baseHome) { - snprintf(buf, sizeof(buf), "%s/" SUBDIR "/%s", baseHome, path); + snprintf(buf, cap, "%s/" SUBDIR "/%s", baseHome, path); } else if (home) { - snprintf( - buf, sizeof(buf), "%s/%s/" SUBDIR "/%s", - home, base.defHome, path - ); + snprintf(buf, cap, "%s/%s/" SUBDIR "/%s", home, base.defHome, path); } else { - errx(EX_CONFIG, "HOME unset"); + errx(EX_USAGE, "HOME unset"); } return buf; } -const char *configPath(const char **dirs, const char *path) { - return basePath(Config, dirs, path); +char *configPath(char *buf, size_t cap, const char *path, int i) { + return basePath(Config, buf, cap, path, i); } -const char *dataPath(const char **dirs, const char *path) { - return basePath(Data, dirs, path); +char *dataPath(char *buf, size_t cap, const char *path, int i) { + return basePath(Data, buf, cap, path, i); } FILE *configOpen(const char *path, const char *mode) { - const char *dirs = NULL; - for (const char *abs; NULL != (abs = configPath(&dirs, path));) { - FILE *file = fopen(abs, mode); + char buf[PATH_MAX]; + for (int i = 0; configPath(buf, sizeof(buf), path, i); ++i) { + FILE *file = fopen(buf, mode); if (file) return file; - if (errno != ENOENT) warn("%s", abs); + if (errno != ENOENT) warn("%s", buf); } - dirs = NULL; - warn("%s", configPath(&dirs, path)); + warn("%s", configPath(buf, sizeof(buf), path, 0)); return NULL; } -void dataMkdir(const char *path) { - const char *dirs = NULL; - path = dataPath(&dirs, path); - int error = mkdir(path, S_IRWXU); - if (error && errno != EEXIST) warn("%s", path); -} - FILE *dataOpen(const char *path, const char *mode) { - const char *dirs = NULL; - for (const char *abs; NULL != (abs = dataPath(&dirs, path));) { - FILE *file = fopen(abs, mode); + char buf[PATH_MAX]; + for (int i = 0; dataPath(buf, sizeof(buf), path, i); ++i) { + FILE *file = fopen(buf, mode); if (file) return file; - if (errno != ENOENT) warn("%s", abs); + if (errno != ENOENT) warn("%s", buf); + } + if (mode[0] != 'r') { + int error = mkdir(dataPath(buf, sizeof(buf), "", 0), S_IRWXU); + if (error && errno != EEXIST) warn("%s", buf); } - if (mode[0] != 'r') dataMkdir(""); - dirs = NULL; - path = dataPath(&dirs, path); - FILE *file = fopen(path, mode); - if (!file) warn("%s", path); + FILE *file = fopen(dataPath(buf, sizeof(buf), path, 0), mode); + if (!file) warn("%s", buf); return file; } |