diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile | 37 | ||||
-rw-r--r-- | README.7 | 48 | ||||
-rw-r--r-- | buffer.c | 9 | ||||
-rw-r--r-- | catgirl.1 | 525 | ||||
-rw-r--r-- | chat.c | 65 | ||||
-rw-r--r-- | chat.h | 40 | ||||
-rw-r--r-- | command.c | 56 | ||||
-rw-r--r-- | complete.c (renamed from cache.c) | 122 | ||||
-rw-r--r-- | config.c | 14 | ||||
-rwxr-xr-x | configure | 8 | ||||
-rw-r--r-- | filter.c | 9 | ||||
-rw-r--r-- | handle.c | 115 | ||||
-rw-r--r-- | input.c | 35 | ||||
-rw-r--r-- | irc.c | 37 | ||||
-rw-r--r-- | log.c | 29 | ||||
-rw-r--r-- | sandman.1 (renamed from scripts/sandman.1) | 0 | ||||
-rw-r--r-- | sandman.m (renamed from scripts/sandman.m) | 13 | ||||
-rw-r--r-- | scripts/.gitignore | 1 | ||||
-rw-r--r-- | scripts/Makefile | 22 | ||||
-rw-r--r-- | scripts/build-chroot.sh | 74 | ||||
-rw-r--r-- | scripts/chroot-man.sh | 2 | ||||
-rw-r--r-- | scripts/chroot-prompt.sh | 7 | ||||
-rw-r--r-- | scripts/reconnect.sh | 10 | ||||
-rw-r--r-- | scripts/sshd_config | 9 | ||||
-rw-r--r-- | ui.c | 25 | ||||
-rw-r--r-- | url.c | 35 | ||||
-rw-r--r-- | window.c | 18 | ||||
-rw-r--r-- | xdg.c | 3 |
29 files changed, 657 insertions, 712 deletions
diff --git a/.gitignore b/.gitignore index b31d1c5..519791d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ catgirl chroot.tar config.mk root/ +sandman tags diff --git a/Makefile b/Makefile index e8ef63a..e08e8e3 100644 --- a/Makefile +++ b/Makefile @@ -8,14 +8,18 @@ CFLAGS += ${CEXTS:%=-Wno-%} LDADD.libtls = -ltls LDADD.ncursesw = -lncursesw +BINS = catgirl +MANS = ${BINS:=.1} + -include config.mk LDLIBS = ${LDADD.libtls} ${LDADD.ncursesw} +LDLIBS.sandman = -framework Cocoa OBJS += buffer.o -OBJS += cache.o OBJS += chat.o OBJS += command.o +OBJS += complete.o OBJS += config.o OBJS += edit.o OBJS += filter.o @@ -28,11 +32,13 @@ OBJS += url.o OBJS += window.o OBJS += xdg.o +OBJS.sandman = sandman.o + TESTS += edit.t dev: tags all check -all: catgirl +all: ${BINS} catgirl: ${OBJS} ${CC} ${LDFLAGS} ${OBJS} ${LDLIBS} -o $@ @@ -41,6 +47,9 @@ ${OBJS}: chat.h edit.o edit.t input.o: edit.h +sandman: ${OBJS.sandman} + ${CC} ${LDFLAGS} ${OBJS.$@} ${LDLIBS.$@} -o $@ + check: ${TESTS} .SUFFIXES: .t @@ -53,25 +62,13 @@ tags: *.[ch] ctags -w *.[ch] clean: - rm -f catgirl ${OBJS} ${TESTS} tags + rm -f ${BINS} ${OBJS} ${OBJS.sandman} ${TESTS} tags -install: catgirl catgirl.1 +install: ${BINS} ${MANS} install -d ${DESTDIR}${BINDIR} ${DESTDIR}${MANDIR}/man1 - install catgirl ${DESTDIR}${BINDIR} - install -m 644 catgirl.1 ${DESTDIR}${MANDIR}/man1 + install ${BINS} ${DESTDIR}${BINDIR} + install -m 644 ${MANS} ${DESTDIR}${MANDIR}/man1 uninstall: - rm -f ${DESTDIR}${BINDIR}/catgirl ${DESTDIR}${MANDIR}/man1/catgirl.1 - -CHROOT_USER = chat -CHROOT_GROUP = ${CHROOT_USER} - -chroot.tar: catgirl catgirl.1 scripts/chroot-prompt.sh scripts/chroot-man.sh -chroot.tar: scripts/build-chroot.sh - sh scripts/build-chroot.sh ${CHROOT_USER} ${CHROOT_GROUP} - -install-chroot: chroot.tar - tar -px -f chroot.tar -C /home/${CHROOT_USER} - -clean-chroot: - rm -fr chroot.tar root + rm -f ${BINS:%=${DESTDIR}${BINDIR}/%} + rm -f ${MANS:%=${DESTDIR}${MANDIR}/man1/%} diff --git a/README.7 b/README.7 index 2886b88..c4f82e8 100644 --- a/README.7 +++ b/README.7 @@ -1,5 +1,5 @@ .\" To view this file: $ man ./README.7 -.Dd July 30, 2022 +.Dd May 8, 2025 .Dt README 7 .Os "Causal Agency" . @@ -88,7 +88,7 @@ Reconnection: when the connection to the server is lost, .Nm exits. -It can be run in a loop +She can be run in a loop or connected to a bouncer, such as .Lk https://git.causal.agency/pounce "pounce" . @@ -109,6 +109,10 @@ TLS is now ubiquitous and certificates are easy to obtain. .El . +.Sh TESTIMONIALS +.Dq catgirl has like the best scrolling i've ever used in a terminal application +.D1 \(em my friend kylie +. .Sh INSTALLING .Nm requires ncurses and @@ -173,14 +177,14 @@ wrapper is provided for macOS to stop and start .Nm on system sleep and wake. -Install it as follows: +To enable him, +configure with: .Bd -literal -offset indent -$ make -C scripts sandman -# make -C scripts install +$ ./configure --enable-sandman .Ed . .Sh FILES -.Bl -tag -width "command.c" -compact +.Bl -tag -width "complete.c" -compact .It Pa chat.h global state and declarations .It Pa chat.c @@ -201,8 +205,8 @@ command handling line wrapping .It Pa edit.c line editing -.It Pa cache.c -ordered cache +.It Pa complete.c +tab complete .It Pa url.c URL detection .It Pa filter.c @@ -213,6 +217,8 @@ chat logging configuration parsing .It Pa xdg.c XDG base directories +.It Pa sandman.m +sleep/wake wrapper for macOS .El . .Pp @@ -222,24 +228,13 @@ example .Xr tmux 1 configuration for multiple networks and automatic reconnects -.It Pa scripts/sandman.m -sleep/wake wrapper for macOS +.It Pa scripts/reconnect.sh +example script to restart +.Xr catgirl 1 +when she gets disconnected .It Pa scripts/notify-send.scpt .Xr notify-send 1 in AppleScript -.It Pa scripts/build-chroot.sh -chroot builder for -.Ox -and -.Fx -.It Pa scripts/chroot-prompt.sh -name prompt wrapper for chroot -.It Pa scripts/chroot-man.sh -.Xr man 1 -implementation for chroot -.It Pa scripts/sshd_config -.Xr sshd 8 -configuration for public chroot .El . .Sh CONTRIBUTING @@ -252,12 +247,9 @@ For sending patches by email, see Mailing list archives are available at .Aq Lk https://causal.agency/list/catgirl.html . . -.Pp -Monetary contributions can be -.Lk https://liberapay.com/june/donate "donated via Liberapay" . -. .Sh SEE ALSO -.Xr catgirl 1 +.Xr catgirl 1 , +.Xr sandman 1 . .Pp IRC bouncer: diff --git a/buffer.c b/buffer.c index f82e553..6a7b412 100644 --- a/buffer.c +++ b/buffer.c @@ -30,7 +30,6 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <sysexits.h> #include <time.h> #include <wchar.h> #include <wctype.h> @@ -50,7 +49,7 @@ struct Buffer { struct Buffer *bufferAlloc(void) { struct Buffer *buffer = calloc(1, sizeof(*buffer)); - if (!buffer) err(EX_OSERR, "calloc"); + if (!buffer) err(1, "calloc"); return buffer; } @@ -107,7 +106,7 @@ static int flow(struct Lines *hard, int cols, const struct Line *soft) { line->heat = soft->heat; line->time = soft->time; line->str = strdup(soft->str); - if (!line->str) err(EX_OSERR, "strdup"); + if (!line->str) err(1, "strdup"); int width = 0; int align = 0; @@ -185,7 +184,7 @@ static int flow(struct Lines *hard, int cols, const struct Line *soft) { size_t cap = StyleCap + align + strlen(&wrap[n]) + 1; line->str = malloc(cap); - if (!line->str) err(EX_OSERR, "malloc"); + if (!line->str) err(1, "malloc"); char *end = &line->str[cap]; str = seprintf(line->str, end, "%*s", (width = align), ""); @@ -209,7 +208,7 @@ int bufferPush( soft->heat = heat; soft->time = time; soft->str = strdup(str); - if (!soft->str) err(EX_OSERR, "strdup"); + if (!soft->str) err(1, "strdup"); if (heat < thresh) return 0; return flow(&buffer->hard, cols, soft); } diff --git a/catgirl.1 b/catgirl.1 index 32eb365..f2a2fbb 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -1,4 +1,4 @@ -.Dd May 29, 2022 +.Dd May 24, 2024 .Dt CATGIRL 1 .Os . @@ -8,7 +8,7 @@ . .Sh SYNOPSIS .Nm -.Op Fl KRelqv +.Op Fl Relqv .Op Fl C Ar copy .Op Fl H Ar hash .Op Fl I Ar highlight @@ -46,9 +46,9 @@ The .Nm IRC client -provides a curses interface -for TLS-only -Internet Relay Chat. +provides a curses interface for +Internet Relay Chat +over TLS. The only required option is .Fl h , the host name to connect to. @@ -62,7 +62,8 @@ in to view the list of .Sx COMMANDS and -.Sx KEY BINDINGS . +.Sx KEY BINDINGS +in this manual. . .Pp Options can be loaded from files @@ -78,50 +79,65 @@ unless the path starts with .Ql \&./ or .Ql \&../ . -Files and flags listed later -on the command line -take precedence over -those listed earlier. +For example, +a configuration file at +.Pa ~/.config/catgirl/tilde +can be loaded by running +.Ql catgirl tilde . . .Pp Each option is placed on a line, and lines beginning with .Ql # are ignored. +An optional +.Ql = +may appear +between an option and its value. The options are listed below following their corresponding flags. +Flags and options in files are processed +in the order they appear on the command line, +so later values override earlier values. . .Bl -tag -width Ds -.It Fl C Ar util | Cm copy No = Ar util -Set the utility used by -.Ic /copy . +.It Fl C Ar util | Cm copy Ar util +Set the utility used by the +.Ic /copy +command. Subsequent .Cm copy -options append arguments to -.Ar util . -The URL to copy is provided to -.Ar util -on standard input. +options add arguments +to the utility. +The URL to copy is provided +to the utility on standard input. The default is the first available of .Xr pbcopy 1 , .Xr wl-copy 1 , -.Xr xclip 1 , +.Xr xclip 1 +or .Xr xsel 1 . . -.It Fl H Ar seed,bound | Cm hash No = Ar seed,bound -Set the initial seed -of the nick and channel -color hash function -and the maximum IRC color value -produced by the function. -The default is 0,75. -To use only colors from -the 16-color terminal set, +.It Fl H Ar seed,bound | Cm hash Ar seed,bound +Set the seed for choosing +nick and channel colours +and the maximum IRC colour value +that will be chosen. +Changing the seed +will randomize the chosen colours, +in case you don't like the ones +chosen for yourself or your crush. +.Pp +The default is 0,75, +which uses colours +in the 256-colour terminal set. +To use only colours +from the 16-colour terminal set, use 0,15. -To disable nick and channel colors, +To disable nick and channel colours, use 0,0. . -.It Fl I Ar pattern | Cm highlight No = Ar pattern +.It Fl I Ar pattern | Cm highlight Ar pattern Add a case-insensitive message highlight pattern, which may contain .Ql * , @@ -132,60 +148,51 @@ wildcards as in .Xr glob 7 . The format of the pattern is as follows: .Bd -ragged -offset indent +.\" FIXME: there's really no reason !user@host should be required .Ar nick Ns Op Ar !user@host Op Ar command Op Ar channel Op Ar message .Ed .Pp The commands which can be matched are: -.Sy INVITE , -.Sy JOIN , -.Sy NICK , -.Sy NOTICE , -.Sy PART , -.Sy PRIVMSG , -.Sy QUIT , -.Sy SETNAME . -. -.It Fl K | Cm kiosk -Disable the -.Ic /copy , -.Ic /debug , -.Ic /exec , -.Ic /join , -.Ic /list , -.Ic /msg , -.Ic /open , -.Ic /part , -.Ic /query , -.Ic /quote -commands. -Replace the username -with a hash of its original value. +.Sy invite , +.Sy join , +.Sy nick , +.Sy notice , +.Sy part , +.Sy privmsg , +.Sy quit , +.Sy setname . +.Pp +For example, +to highlight whenever your crush +joins your favourite channel: +.Pp +.Dl highlight crush!*@* join #channel . -.It Fl N Ar util | Cm notify No = Ar util +.It Fl N Ar util | Cm notify Ar util Send notifications using a utility. Subsequent .Cm notify -options append arguments to -.Ar util . +options add arguments +to the utility. The window name and message -are provided to -.Ar util +are provided to the utility as two additional arguments, appropriate for .Xr notify-send 1 . . -.It Fl O Ar util | Cm open No = Ar util -Set the utility used by -.Ic /open . +.It Fl O Ar util | Cm open Ar util +Set the utility used by the +.Ic /open +command. Subsequent .Cm open -options append arguments to -.Ar util . -The URL to open is provided to -.Ar util -as an argument. +options add arguments +to the utility. +The URL to open is provided +to the utility as an additional argument. The default is the first available of -.Xr open 1 , +.Xr open 1 +or .Xr xdg-open 1 . . .It Fl R | Cm restrict @@ -201,58 +208,75 @@ option, and viewing this manual with .Ic /help . . -.It Fl S Ar host | Cm bind No = Ar host +.It Fl S Ar host | Cm bind Ar host Bind to source address .Ar host when connecting to the server. -To connect from any address -over IPv4 only, +To connect from any IPv4 address, use 0.0.0.0. -To connect from any address -over IPv6 only, +To connect from any IPv6 address, use ::. . -.It Fl T Ns Oo Ar format Oc | Cm timestamp Op = Ar format -Show timestamps by default, -in the specified -.Xr strftime 3 -.Ar format . -The format string may contain -raw IRC formatting codes. -The default format is -.Qq \&%X . +.It Fl T Ns Oo Ar format Oc | Cm timestamp Op Ar format +Show timestamps by default. +The optional +.Ar format +string is interpreted by +.Xr strftime 3 . +The string may contain +raw IRC formatting codes, +if you can figure out +how to enter them. . -.It Fl a Ar user : Ns Ar pass | Cm sasl-plain No = Ar user : Ns Ar pass -Authenticate as -.Ar user -with -.Ar pass -using SASL PLAIN. +.It Fl a Ar user : Ns Ar pass | Cm sasl-plain Ar user : Ns Ar pass +Authenticate with NickServ +during connection using SASL PLAIN. +.Nm +will disconnect +if authentication fails. Leave .Ar pass -blank to prompt for the password. +blank to prompt for the password when +.Nm +starts. . -.It Fl c Ar path | Cm cert No = Ar path -Load the TLS client certificate from -.Ar path . -The -.Ar path -is searched for in the same manner -as configuration files. -If the private key is in a separate file, -it is loaded with -.Cm priv . -With -.Cm sasl-external , -authenticate using SASL EXTERNAL. -Certificates can be generated with -.Fl g . +.It Fl c Ar path | Cm cert Ar path +Connect using a TLS client certificate +loaded from +.Ar path , +which is searched for +in the same manner as configuration files. +If the private key +is in a separate file, +additionally specify it with the +.Cm priv +option. +.Pp +To use this certificate +to authenticate to NickServ +using CertFP, +use the +.Cm sasl-external +option. +See +.Sx Configuring CertFP . +.Pp +Client certificates +can be generated with the +.Fl g +flag. . .It Fl e | Cm sasl-external -Authenticate using SASL EXTERNAL, -also known as CertFP. -The TLS client certificate is loaded with -.Cm cert . +Authenticate to NickServ +during connection using CertFP +via SASL EXTERNAL. +.Nm +will disconnect +if authentication fails. +The client certificate +must be specified with the +.Cm cert +option. See .Sx Configuring CertFP . . @@ -262,11 +286,11 @@ Generate a TLS client certificate using and write it to .Ar path . . -.It Fl h Ar host | Cm host No = Ar host -Connect to +.It Fl h Ar host | Cm host Ar host +Connect to the IRC server .Ar host . . -.It Fl i Ar pattern | Cm ignore No = Ar pattern +.It Fl i Ar pattern | Cm ignore Ar pattern Add a case-insensitive message ignore pattern, which may contain .Ql * , @@ -281,56 +305,69 @@ The format of the pattern is as follows: .Ed .Pp The commands which can be matched are: -.Sy INVITE , -.Sy JOIN , -.Sy NICK , -.Sy NOTICE , -.Sy PART , -.Sy PRIVMSG , -.Sy QUIT , -.Sy SETNAME . +.Sy invite , +.Sy join , +.Sy nick , +.Sy notice , +.Sy part , +.Sy privmsg , +.Sy quit , +.Sy setname . +.Pp +Visibility of ignored messages +can be toggled using +.Ic M-- +and +.Ic M-+ . . -.It Fl j Ar channels Oo Ar keys Oc | Cm join No = Ar channels Oo Ar keys Oc +.It Fl j Ar channels Oo Ar keys Oc | Cm join Ar channels Oo Ar keys Oc Join the comma-separated list of .Ar channels with the optional comma-separated list of channel .Ar keys . +No spaces may appear in either list. . -.It Fl k Ar path | Cm priv No = Ar priv -Load the TLS client private key from -.Ar path . -The -.Ar path -is searched for in the same manner -as configuration files. +.It Fl k Ar path | Cm priv Ar path +Load the TLS client private key +for a certificate loaded with the +.Cm cert +option from +.Ar path , +which is search for +in the same manner as configuration files. . .It Fl l | Cm log -Log chat events to files in paths -.Pa $XDG_DATA_HOME/catgirl/log/network/channel/YYYY-MM-DD.log . +Log messages to files in +.Pa $XDG_DATA_HOME/catgirl/log +.Po +usually +.Pa ~/.local/share/catgirl/log +.Pc . +Directories are created +for each network and channel, +and files are created for each date +in the format +.Pa YYYY-MM-DD.log . . -.It Fl m Ar mode | Cm mode No = Ar mode -Set the user -.Ar mode . +.It Fl m Ar modes | Cm mode Ar modes +Set user modes as soon as possible +after connecting. . -.It Fl n Ar nick Oo Ar ... Oc | Cm nick No = Ar nick Oo Ar ... Oc -Set nickname to -.Ar nick . -The default nickname is -the value of the environment variable +.It Fl n Ar nick Oo Ar ... Oc | Cm nick Ar nick Oo Ar ... Oc +Set the nickname with optional fallbacks, +should one nick be unavailable. +Each nick is treated as a highlight word. +The default nickname is the value of .Ev USER . -Additional space-separated nicks -will be tried in order -if the first is not available, -and all nicks -are treated as highlight words. . .It Fl o -Print the server certificate chain -to standard output in PEM format -and exit. +Connect to the server +only to obtain its certificate chain +and write it to standard output +in PEM format. . -.It Fl p Ar port | Cm port No = Ar port -Connect to +.It Fl p Ar port | Cm port Ar port +Connect to the IRC server on .Ar port . The default port is 6697. . @@ -339,17 +376,27 @@ Raise the default message visibility threshold for new windows, hiding general events (joins, quits, etc.). +The threshold can be lowered with +.Ic M-- . . -.It Fl r Ar real | Cm real No = Ar real -Set realname to -.Ar real . -The default realname is the same as the nickname. +.It Fl r Ar real | Cm real Ar real +Set the +.Dq realname +which appears in +.Ic /whois . +The default is the same as the nickname. +This is a good place to add your pronouns. . -.It Fl s Ar name | Cm save No = Ar name -Save and load the contents of windows from +.It Fl s Ar name | Cm save Ar name +Persist windows and their scrollback +in a file called .Ar name in -.Pa $XDG_DATA_DIRS/catgirl , +.Pa $XDG_DATA_DIRS/catgirl +.Po +usually +.Pa ~/.local/share/catgirl +.Pc , or an absolute or relative path if .Ar name starts with @@ -358,39 +405,50 @@ starts with or .Ql \&../ . . -.It Fl t Ar path | Cm trust No = Ar path -Trust the self-signed certificate -loaded from -.Ar path -and disable server name verification. -The -.Ar path -is searched for in the same manner -as configuration files. +.It Fl t Ar path | Cm trust Ar path +Trust the self-signed certificate in +.Ar path , +which is searched for +in the same manner as configuration files. +Server name verification is also disabled. See .Sx Connecting to Servers with Self-signed Certificates . . -.It Fl u Ar user | Cm user No = Ar user -Set username to -.Ar user . -The default username is the same as the nickname. +.It Fl u Ar user | Cm user Ar user +Set the username. +This is almost entirely irrelevant, +except that it's more likely to remain stable, +and +.Nm +uses it to choose nick colours. +The default is the same as the nickname. . .It Fl v | Cm debug -Log raw IRC messages to the +Log raw IRC protocol to the .Sy <debug> -window +window, as well as standard error if it is not a terminal. . -.It Fl w Ar pass | Cm pass No = Ar pass -Log in with the server password -.Ar pass . +.It Fl w Ar pass | Cm pass Ar pass +Connect using a server password. Leave .Ar pass -blank to prompt for the password. +blank +.Po +using an +.Ql = +.Pc +to prompt for the password when +.Nm +starts. .El . .Ss Configuring CertFP +CertFP allows you to +authenticate with NickServ during connection +using a TLS client certificate +rather than your account password. .Bl -enum .It Generate a new TLS client certificate: @@ -398,32 +456,34 @@ Generate a new TLS client certificate: $ catgirl -g ~/.config/catgirl/example.pem .Ed .It -Connect to the server using the certificate: +Connect to the server using the certificate +by adding the following configuration: .Bd -literal -offset indent -cert = example.pem -# or: $ catgirl -c example.pem +cert example.pem .Ed .It -Identify with services or use -.Cm sasl-plain , +Identify with NickServ, then add the certificate fingerprint to your account: .Bd -literal -offset indent /ns CERT ADD .Ed .It -Enable SASL EXTERNAL +Enable SASL EXTERNAL in your configuration to require successful authentication when connecting (not possible on all networks): .Bd -literal -offset indent -cert = example.pem +cert example.pem sasl-external -# or: $ catgirl -e -c example.pem .Ed .El . .Ss Connecting to Servers with Self-signed Certificates +If connecting to a server fails +with a certificate verification error +due to a self-signed certificate, +it needs to be trusted manually. .Bl -enum .It Connect to the server @@ -436,8 +496,7 @@ Configure .Nm to trust the certificate: .Bd -literal -offset indent -trust = example.pem -# or: $ catgirl -t example.pem +trust example.pem .Ed .El . @@ -445,13 +504,13 @@ trust = example.pem The .Nm interface is split -into three areas. +into three main areas. . .Ss Status Line The top line of the terminal shows window statuses. Only the currently active window -and windows with activity are listed. +and windows with activity are shown. The status line for a window might look like this: .Bd -literal -offset indent @@ -462,7 +521,8 @@ The number on the left is the window number. Following it may be one of .Ql - , -.Ql + , +.Ql + +or .Ql ++ , as well as .Ql = . @@ -478,11 +538,11 @@ indicates the number of unread messages. The number following .Ql ~ indicates how many lines -are below the scroll position. +are below the current scroll position. An .Ql @ indicates that there is unsent input -in the window's +waiting in the window's .Sx Input Line . .Pp .Nm @@ -512,7 +572,11 @@ with the nick between hyphens. .Pp Blank lines are inserted into the chat -as unread markers. +as unread markers +whenever there are messages +in a window that is not active +or the terminal is not focused +(in some terminal emulators). .Pp While scrolling, the most recent 5 lines of chat @@ -540,68 +604,106 @@ starting at the point where it will be split into a second message. . .Sh COMMANDS -Any unique prefix can be used to abbreviate a command. +Commands can be abbreviated +if no other command +shares the same prefix. For example, .Ic /join can be typed -.Ic /j . +.Ic /j , +and +.Ic /window +can be typed +.Ic /wi . . .Ss Chat Commands .Bl -tag -width Ds .It Ic /away Op Ar message Set or clear your away status. +This is sent in reply to private messages +and shown in +.Ic /whois . .It Ic /cs Ar command -Send a command to ChanServ. +Send a command to ChanServ, +the service for managing registered channels. .It Ic /invite Ar nick -Invite a user to the channel. +Invite someone to the channel. .It Ic /join Op Ar channel Op Ar key Join the named channel, -the current channel, +the current channel (if you've left), or the channel you've been invited to. -.It Ic /list Op Ar channel -List channels. +.It Ic /list Op Ar search +List channels, their user counts and their topics. +The +.Ar search +can usually contain glob-style wildcards. .It Ic /me Op Ar action Send an action message. +These are used to write messages in third person. .It Ic /msg Ar nick message -Send a private message. +Send a private message to someone. .It Ic /names -List users in the channel. +List the users in the channel. .It Ic /nick Ar nick -Change nicknames. +Change your nickname. .It Ic /notice Ar message Send a notice. +It's best not to do this. .It Ic /ns Ar command -Send a command to NickServ. +Send a command to NickServ, +the service for managing your account. .It Ic /ops List channel operators. +They can kick or ban someone from the channel. .It Ic /part Op Ar message Leave the channel. +Use +.Ic /close +if you want to close the window afterwards. .It Ic /query Ar nick -Start a private conversation. +Start a private conversation with someone. .It Ic /quit Op Ar message -Quit IRC. +Disconnect from IRC and close +.Nm . +You can do this even quicker with +.Ic C-c . .It Ic /quote Ar command Send a raw IRC command. -Use +Often +.Nm +will not know how to interpret the results. +You can use .Ic M-- -to show unknown replies. +to show unknown server responses +in the +.Sy <network> +or channel windows. .It Ic /say Ar message Send a regular message. +This is useful +if the message you want to send +begins with a slash. .It Ic /setname Ar name -Update realname -if supported by the server. +Update your +.Dq realname +if the server supports it. +This may be broadcast +to other users +with clients that support it. .It Ic /topic Op Ar topic Show or set the topic of the channel. Press .Ic Tab -twice to copy the current topic. +twice immediately after +.Ic /topic +to copy the current topic. .It Ic /whois Op Ar nick Query information about a user or yourself. .It Ic /whowas Ar nick Query past information about a user. .El . -.Ss UI Commands +.Ss Interface Commands .Bl -tag -width Ds .It Ic /close Op Ar name | num Close the named, numbered or current window. @@ -649,6 +751,9 @@ use the option. .It Ic /move Oo Ar name Oc Ar num Move the named or current window to number. +.It Ic /o ... +Alias of +.Ic /open . .It Ic /open Op Ar count Open each of .Ar count @@ -922,9 +1027,7 @@ The .Nm client exits 0 if requested by the user, -.Dv EX_UNAVAILABLE -(69) -if the connection is lost, +69 if the connection is lost, and >0 if any other error occurs. . .Sh EXAMPLES diff --git a/chat.c b/chat.c index 39b1a93..bc23c3f 100644 --- a/chat.c +++ b/chat.c @@ -41,7 +41,6 @@ #include <sys/stat.h> #include <sys/time.h> #include <sys/wait.h> -#include <sysexits.h> #include <time.h> #include <tls.h> #include <unistd.h> @@ -70,7 +69,7 @@ static void genCert(const char *path) { "-nodes", "-subj", subj, "-out", path, "-keyout", path, NULL ); - err(EX_UNAVAILABLE, "openssl"); + err(127, "openssl"); } char *idNames[IDCap] = { @@ -93,7 +92,7 @@ static void exitSave(void) { int error = uiSave(); if (error) { warn("%s", save); - _exit(EX_IOERR); + _exit(1); } } @@ -104,7 +103,7 @@ int utilPipe[2] = { -1, -1 }; static void execRead(void) { char buf[1024]; ssize_t len = read(execPipe[0], buf, sizeof(buf) - 1); - if (len < 0) err(EX_IOERR, "read"); + if (len < 0) err(1, "read"); if (!len) return; buf[len] = '\0'; for (char *ptr = buf; ptr;) { @@ -116,7 +115,7 @@ static void execRead(void) { static void utilRead(void) { char buf[1024]; ssize_t len = read(utilPipe[0], buf, sizeof(buf) - 1); - if (len < 0) err(EX_IOERR, "read"); + if (len < 0) err(1, "read"); if (!len) return; buf[len] = '\0'; for (char *ptr = buf; ptr;) { @@ -135,7 +134,7 @@ static void parseHash(char *str) { static void parsePlain(char *str) { self.plainUser = strsep(&str, ":"); - if (!str) errx(EX_USAGE, "SASL PLAIN missing colon"); + if (!str) errx(1, "SASL PLAIN missing colon"); self.plainPass = str; } @@ -159,27 +158,27 @@ static void sandboxEarly(bool log) { if (log) { char buf[PATH_MAX]; int error = unveil(dataPath(buf, sizeof(buf), "log", 0), "wc"); - if (error) err(EX_OSERR, "unveil"); + if (error) err(1, "unveil"); ptr = seprintf(ptr, end, " wpath cpath"); } if (!self.restricted) { int error = unveil("/", "x"); - if (error) err(EX_OSERR, "unveil"); + if (error) err(1, "unveil"); ptr = seprintf(ptr, end, " proc exec"); } promisesInitial = ptr; ptr = seprintf(ptr, end, " inet dns"); int error = pledge(promises, NULL); - if (error) err(EX_OSERR, "pledge"); + if (error) err(1, "pledge"); } static void sandboxLate(int irc) { (void)irc; *promisesInitial = '\0'; int error = pledge(promises, NULL); - if (error) err(EX_OSERR, "pledge"); + if (error) err(1, "pledge"); } #elif defined __FreeBSD__ @@ -202,7 +201,7 @@ static void sandboxLate(int irc) { || caph_rights_limit( irc, cap_rights_init(&rights, CAP_SEND, CAP_RECV, CAP_EVENT) ); - if (error) err(EX_OSERR, "cap_rights_limit"); + if (error) err(1, "cap_rights_limit"); // caph_cache_tzdata(3) doesn't load UTC info, which we need for // certificate verification. gmtime(3) does. @@ -210,7 +209,7 @@ static void sandboxLate(int irc) { gmtime(&(time_t) { time(NULL) }); error = cap_enter(); - if (error) err(EX_OSERR, "cap_enter"); + if (error) err(1, "cap_enter"); } #else @@ -245,7 +244,6 @@ int main(int argc, char *argv[]) { { .val = 'C', .name = "copy", required_argument }, { .val = 'H', .name = "hash", required_argument }, { .val = 'I', .name = "highlight", required_argument }, - { .val = 'K', .name = "kiosk", no_argument }, { .val = 'N', .name = "notify", required_argument }, { .val = 'O', .name = "open", required_argument }, { .val = 'R', .name = "restrict", no_argument }, @@ -286,7 +284,6 @@ int main(int argc, char *argv[]) { break; case 'C': utilPush(&urlCopyUtil, optarg); break; case 'H': parseHash(optarg); break; case 'I': filterAdd(Hot, optarg); - break; case 'K': self.kiosk = true; break; case 'N': utilPush(&uiNotifyUtil, optarg); break; case 'O': utilPush(&urlOpenUtil, optarg); break; case 'R': self.restricted = true; @@ -319,47 +316,40 @@ int main(int argc, char *argv[]) { break; case 'u': user = optarg; break; case 'v': self.debug = true; break; case 'w': pass = optarg; - break; default: return EX_USAGE; + break; default: return 1; } } - if (!host) errx(EX_USAGE, "host required"); + if (!host) errx(1, "host required"); if (printCert) { #ifdef __OpenBSD__ int error = pledge("stdio inet dns", NULL); - if (error) err(EX_OSERR, "pledge"); + if (error) err(1, "pledge"); #endif ircConfig(true, NULL, NULL, NULL); ircConnect(bind, host, port); ircPrintCert(); ircClose(); - return EX_OK; + return 0; } if (!self.nicks[0]) self.nicks[0] = getenv("USER"); - if (!self.nicks[0]) errx(EX_CONFIG, "USER unset"); + if (!self.nicks[0]) errx(1, "USER unset"); if (!user) user = self.nicks[0]; if (!real) real = self.nicks[0]; - if (self.kiosk) { - char *hash; - int n = asprintf(&hash, "%08" PRIx32, _hash(user)); - if (n < 0) err(EX_OSERR, "asprintf"); - user = hash; - } - if (pass && !pass[0]) { char *buf = malloc(512); - if (!buf) err(EX_OSERR, "malloc"); + if (!buf) err(1, "malloc"); pass = readpassphrase("Server password: ", buf, 512, 0); - if (!pass) errx(EX_IOERR, "unable to read passphrase"); + if (!pass) errx(1, "unable to read passphrase"); } if (self.plainPass && !self.plainPass[0]) { char *buf = malloc(512); - if (!buf) err(EX_OSERR, "malloc"); + if (!buf) err(1, "malloc"); self.plainPass = readpassphrase("Account password: ", buf, 512, 0); - if (!self.plainPass) errx(EX_IOERR, "unable to read passphrase"); + if (!self.plainPass) errx(1, "unable to read passphrase"); } // Modes defined in RFC 1459: @@ -374,7 +364,7 @@ int main(int argc, char *argv[]) { set(&network.name, host); set(&self.nick, "*"); - inputCache(); + inputCompletion(); ircConfig(insecure, trust, cert, priv); @@ -418,10 +408,9 @@ int main(int argc, char *argv[]) { signal(SIGTERM, signalHandler); signal(SIGCHLD, signalHandler); - bool pipes = !self.kiosk && !self.restricted; - if (pipes) { + if (!self.restricted) { int error = pipe(utilPipe) || pipe(execPipe); - if (error) err(EX_OSERR, "pipe"); + if (error) err(1, "pipe"); fcntl(utilPipe[0], F_SETFD, FD_CLOEXEC); fcntl(utilPipe[1], F_SETFD, FD_CLOEXEC); @@ -437,8 +426,8 @@ int main(int argc, char *argv[]) { { .events = POLLIN, .fd = execPipe[0] }, }; while (!self.quit) { - int nfds = poll(fds, (pipes ? ARRAY_LEN(fds) : 2), -1); - if (nfds < 0 && errno != EINTR) err(EX_IOERR, "poll"); + int nfds = poll(fds, (self.restricted ? 2 : ARRAY_LEN(fds)), -1); + if (nfds < 0 && errno != EINTR) err(1, "poll"); if (nfds > 0) { if (fds[0].revents) inputRead(); if (fds[1].revents) ircRecv(); @@ -456,12 +445,12 @@ int main(int argc, char *argv[]) { .it_interval.tv_sec = 30, }; int error = setitimer(ITIMER_REAL, &timer, NULL); - if (error) err(EX_OSERR, "setitimer"); + if (error) err(1, "setitimer"); } if (signals[SIGALRM]) { signals[SIGALRM] = 0; if (ping) { - errx(EX_UNAVAILABLE, "ping timeout"); + errx(69, "ping timeout"); } else { ircFormat("PING nyaa\r\n"); ping = true; diff --git a/chat.h b/chat.h index 15c757f..369747c 100644 --- a/chat.h +++ b/chat.h @@ -35,7 +35,6 @@ #include <stdio.h> #include <string.h> #include <strings.h> -#include <sysexits.h> #include <time.h> #include <wchar.h> @@ -131,7 +130,7 @@ static inline uint idFor(const char *name) { if (idNext == IDCap) return Network; idNames[idNext] = strdup(name); idColors[idNext] = Default; - if (!idNames[idNext]) err(EX_OSERR, "strdup"); + if (!idNames[idNext]) err(1, "strdup"); return idNext++; } @@ -202,7 +201,6 @@ enum Cap { extern struct Self { bool debug; - bool kiosk; bool restricted; size_t pos; enum Cap caps; @@ -222,7 +220,7 @@ extern struct Self { static inline void set(char **field, const char *value) { free(*field); *field = strdup(value); - if (!*field) err(EX_OSERR, "strdup"); + if (!*field) err(1, "strdup"); } #define ENUM_TAG \ @@ -274,7 +272,7 @@ static inline void utilPush(struct Util *util, const char *arg) { if (1 + util->argc < UtilCap) { util->argv[util->argc++] = arg; } else { - errx(EX_CONFIG, "too many utility arguments"); + errx(1, "too many utility arguments"); } } @@ -304,7 +302,7 @@ const char *commandIsPrivmsg(uint id, const char *input); const char *commandIsNotice(uint id, const char *input); const char *commandIsAction(uint id, const char *input); size_t commandWillSplit(uint id, const char *input); -void commandCache(void); +void commandCompletion(void); enum Heat { Ice, @@ -346,7 +344,7 @@ void inputWait(void); void inputUpdate(void); bool inputPending(uint id); void inputRead(void); -void inputCache(void); +void inputCompletion(void); int inputSave(FILE *file); void inputLoad(FILE *file, size_t version); @@ -408,26 +406,22 @@ int bufferReflow( struct Buffer *buffer, int cols, enum Heat thresh, size_t tail ); -struct Entry { - enum Color color; - uint prefixBits; -}; struct Cursor { uint gen; struct Node *node; - struct Entry *entry; }; -const struct Entry *cacheGet(uint id, const char *key); -struct Entry *cacheInsert(bool touch, uint id, const char *key); -void cacheReplace(bool touch, const char *old, const char *new); -void cacheRemove(uint id, const char *key); -void cacheClear(uint id); -const char *cacheComplete(struct Cursor *curs, uint id, const char *prefix); -const char *cacheSearch(struct Cursor *curs, uint id, const char *substr); -uint cacheNextID(struct Cursor *curs, const char *key); -const char *cacheNextKey(struct Cursor *curs, uint id); -void cacheAccept(struct Cursor *curs); -void cacheReject(struct Cursor *curs); +void completePush(uint id, const char *str, enum Color color); +void completePull(uint id, const char *str, enum Color color); +void completeReplace(const char *old, const char *new); +void completeRemove(uint id, const char *str); +enum Color completeColor(uint id, const char *str); +uint *completeBits(uint id, const char *str); +const char *completePrefix(struct Cursor *curs, uint id, const char *prefix); +const char *completeSubstr(struct Cursor *curs, uint id, const char *substr); +const char *completeEach(struct Cursor *curs, uint id); +uint completeEachID(struct Cursor *curs, const char *str); +void completeAccept(struct Cursor *curs); +void completeReject(struct Cursor *curs); extern struct Util urlOpenUtil; extern struct Util urlCopyUtil; diff --git a/command.c b/command.c index 4fb58da..9b2b4eb 100644 --- a/command.c +++ b/command.c @@ -139,7 +139,7 @@ static void commandMsg(uint id, char *params) { char *nick = strsep(¶ms, " "); uint msg = idFor(nick); if (idColors[msg] == Default) { - idColors[msg] = cacheGet(id, nick)->color; + idColors[msg] = completeColor(id, nick); } if (params) { splitMessage("PRIVMSG", msg, params); @@ -227,12 +227,12 @@ static void commandOps(uint id, char *params) { ); bool first = true; struct Cursor curs = {0}; - for (const char *nick; (nick = cacheNextKey(&curs, id));) { - char prefix = bitPrefix(curs.entry->prefixBits); + for (const char *nick; (nick = completeEach(&curs, id));) { + char prefix = bitPrefix(*completeBits(id, nick)); if (!prefix || prefix == '+') continue; ptr = seprintf( ptr, end, "%s\3%02d%c%s\3", - (first ? "" : ", "), curs.entry->color, prefix, nick + (first ? "" : ", "), completeColor(id, nick), prefix, nick ); first = false; } @@ -403,7 +403,7 @@ static void commandQuery(uint id, char *params) { if (!params) return; uint query = idFor(params); if (idColors[query] == Default) { - idColors[query] = cacheGet(id, params)->color; + idColors[query] = completeColor(id, params); } windowShow(windowFor(query)); } @@ -420,10 +420,10 @@ static void commandWindow(uint id, char *params) { return; } struct Cursor curs = {0}; - for (const char *match; (match = cacheSearch(&curs, None, params));) { - id = idFind(match); + for (const char *str; (str = completeSubstr(&curs, None, params));) { + id = idFind(str); if (!id) continue; - cacheAccept(&curs); + completeAccept(&curs); windowShow(windowFor(id)); break; } @@ -516,7 +516,7 @@ static void commandExec(uint id, char *params) { execID = id; pid_t pid = fork(); - if (pid < 0) err(EX_OSERR, "fork"); + if (pid < 0) err(1, "fork"); if (pid) return; setsid(); @@ -527,7 +527,7 @@ static void commandExec(uint id, char *params) { const char *shell = getenv("SHELL") ?: "/bin/sh"; execl(shell, shell, "-c", params, NULL); warn("%s", shell); - _exit(EX_UNAVAILABLE); + _exit(127); } static void commandHelp(uint id, char *params) { @@ -545,7 +545,7 @@ static void commandHelp(uint id, char *params) { uiHide(); pid_t pid = fork(); - if (pid < 0) err(EX_OSERR, "fork"); + if (pid < 0) err(1, "fork"); if (pid) return; char buf[256]; @@ -554,13 +554,12 @@ static void commandHelp(uint id, char *params) { execlp("man", "man", "1", "catgirl", NULL); dup2(utilPipe[1], STDERR_FILENO); warn("man"); - _exit(EX_UNAVAILABLE); + _exit(127); } enum Flag { BIT(Multiline), BIT(Restrict), - BIT(Kiosk), }; static const struct Handler { @@ -572,37 +571,37 @@ static const struct Handler { { "/away", commandAway, 0, 0 }, { "/ban", commandBan, 0, 0 }, { "/close", commandClose, 0, 0 }, - { "/copy", commandCopy, Restrict | Kiosk, 0 }, + { "/copy", commandCopy, Restrict, 0 }, { "/cs", commandCS, 0, 0 }, - { "/debug", commandDebug, Kiosk, 0 }, + { "/debug", commandDebug, 0, 0 }, { "/deop", commandDeop, 0, 0 }, { "/devoice", commandDevoice, 0, 0 }, { "/except", commandExcept, 0, 0 }, - { "/exec", commandExec, Multiline | Restrict | Kiosk, 0 }, + { "/exec", commandExec, Multiline | Restrict, 0 }, { "/help", commandHelp, 0, 0 }, // Restrict special case. { "/highlight", commandHighlight, 0, 0 }, { "/ignore", commandIgnore, 0, 0 }, { "/invex", commandInvex, 0, 0 }, { "/invite", commandInvite, 0, 0 }, - { "/join", commandJoin, Kiosk, 0 }, + { "/join", commandJoin, 0, 0 }, { "/kick", commandKick, 0, 0 }, - { "/list", commandList, Kiosk, 0 }, + { "/list", commandList, 0, 0 }, { "/me", commandMe, Multiline, 0 }, { "/mode", commandMode, 0, 0 }, { "/move", commandMove, 0, 0 }, - { "/msg", commandMsg, Multiline | Kiosk, 0 }, + { "/msg", commandMsg, Multiline, 0 }, { "/names", commandNames, 0, 0 }, { "/nick", commandNick, 0, 0 }, { "/notice", commandNotice, Multiline, 0 }, { "/ns", commandNS, 0, 0 }, - { "/o", commandOpen, Restrict | Kiosk, 0 }, + { "/o", commandOpen, Restrict, 0 }, { "/op", commandOp, 0, 0 }, - { "/open", commandOpen, Restrict | Kiosk, 0 }, + { "/open", commandOpen, Restrict, 0 }, { "/ops", commandOps, 0, 0 }, - { "/part", commandPart, Kiosk, 0 }, - { "/query", commandQuery, Kiosk, 0 }, + { "/part", commandPart, 0, 0 }, + { "/query", commandQuery, 0, 0 }, { "/quit", commandQuit, 0, 0 }, - { "/quote", commandQuote, Multiline | Kiosk, 0 }, + { "/quote", commandQuote, Multiline, 0 }, { "/say", commandPrivmsg, Multiline, 0 }, { "/setname", commandSetname, 0, CapSetname }, { "/topic", commandTopic, 0, 0 }, @@ -672,7 +671,6 @@ size_t commandWillSplit(uint id, const char *input) { static bool commandAvailable(const struct Handler *handler) { if (handler->flags & Restrict && self.restricted) return false; - if (handler->flags & Kiosk && self.kiosk) return false; if (handler->caps && (handler->caps & self.caps) != handler->caps) { return false; } @@ -695,8 +693,8 @@ void command(uint id, char *input) { struct Cursor curs = {0}; const char *cmd = strsep(&input, " "); - const char *unique = cacheComplete(&curs, None, cmd); - if (unique && !cacheComplete(&curs, None, cmd)) { + const char *unique = completePrefix(&curs, None, cmd); + if (unique && !completePrefix(&curs, None, cmd)) { cmd = unique; } @@ -724,9 +722,9 @@ void command(uint id, char *input) { handler->fn(id, input); } -void commandCache(void) { +void commandCompletion(void) { for (size_t i = 0; i < ARRAY_LEN(Commands); ++i) { if (!commandAvailable(&Commands[i])) continue; - cacheInsert(false, None, Commands[i].cmd); + completePush(None, Commands[i].cmd, Default); } } diff --git a/cache.c b/complete.c index 970bd9c..d7108e6 100644 --- a/cache.c +++ b/complete.c @@ -26,34 +26,32 @@ */ #include <err.h> -#include <stdio.h> #include <stdlib.h> #include <string.h> -#include <sysexits.h> #include "chat.h" struct Node { uint id; - char *key; - struct Entry entry; + char *str; + enum Color color; + uint bits; struct Node *prev; struct Node *next; }; -static const struct Entry DefaultEntry = { .color = Default }; - static uint gen; static struct Node *head; static struct Node *tail; -static struct Node *alloc(uint id, const char *key) { +static struct Node *alloc(uint id, const char *str, enum Color color) { struct Node *node = calloc(1, sizeof(*node)); - if (!node) err(EX_OSERR, "calloc"); + if (!node) err(1, "calloc"); node->id = id; - node->key = strdup(key); - node->entry = DefaultEntry; - if (!node->key) err(EX_OSERR, "strdup"); + node->str = strdup(str); + if (!node->str) err(1, "strdup"); + node->color = color; + node->bits = 0; return node; } @@ -85,76 +83,68 @@ static struct Node *append(struct Node *node) { return node; } -static struct Node *find(uint id, const char *key) { +static struct Node *find(uint id, const char *str) { for (struct Node *node = head; node; node = node->next) { - if (node->id != id) continue; - if (strcmp(node->key, key)) continue; - return node; + if (node->id == id && !strcmp(node->str, str)) return node; } return NULL; } -static struct Node *insert(bool touch, uint id, const char *key) { - struct Node *node = find(id, key); - if (node && touch) { - return prepend(detach(node)); - } else if (node) { - return node; - } else if (touch) { - return prepend(alloc(id, key)); +void completePush(uint id, const char *str, enum Color color) { + struct Node *node = find(id, str); + if (node) { + if (color != Default) node->color = color; } else { - return append(alloc(id, key)); + append(alloc(id, str, color)); } } -const struct Entry *cacheGet(uint id, const char *key) { - struct Node *node = find(id, key); - return (node ? &node->entry : &DefaultEntry); -} - -struct Entry *cacheInsert(bool touch, uint id, const char *key) { - return &insert(touch, id, key)->entry; +void completePull(uint id, const char *str, enum Color color) { + struct Node *node = find(id, str); + if (node) { + if (color != Default) node->color = color; + prepend(detach(node)); + } else { + prepend(alloc(id, str, color)); + } } -void cacheReplace(bool touch, const char *old, const char *new) { +void completeReplace(const char *old, const char *new) { struct Node *next = NULL; for (struct Node *node = head; node; node = next) { next = node->next; - if (strcmp(node->key, old)) continue; - free(node->key); - node->key = strdup(new); - if (!node->key) err(EX_OSERR, "strdup"); - if (touch) prepend(detach(node)); + if (strcmp(node->str, old)) continue; + free(node->str); + node->str = strdup(new); + if (!node->str) err(1, "strdup"); + prepend(detach(node)); } } -void cacheRemove(uint id, const char *key) { - gen++; +void completeRemove(uint id, const char *str) { struct Node *next = NULL; for (struct Node *node = head; node; node = next) { next = node->next; if (id && node->id != id) continue; - if (strcmp(node->key, key)) continue; + if (str && strcmp(node->str, str)) continue; detach(node); - free(node->key); + free(node->str); free(node); - if (id) break; } + gen++; } -void cacheClear(uint id) { - gen++; - struct Node *next = NULL; - for (struct Node *node = head; node; node = next) { - next = node->next; - if (node->id != id) continue; - detach(node); - free(node->key); - free(node); - } +enum Color completeColor(uint id, const char *str) { + struct Node *node = find(id, str); + return (node ? node->color : Default); +} + +uint *completeBits(uint id, const char *str) { + struct Node *node = find(id, str); + return (node ? &node->bits : NULL); } -const char *cacheComplete(struct Cursor *curs, uint id, const char *prefix) { +const char *completePrefix(struct Cursor *curs, uint id, const char *prefix) { size_t len = strlen(prefix); if (curs->gen != gen) curs->node = NULL; for ( @@ -163,14 +153,12 @@ const char *cacheComplete(struct Cursor *curs, uint id, const char *prefix) { curs->node = curs->node->next ) { if (curs->node->id && curs->node->id != id) continue; - if (strncasecmp(curs->node->key, prefix, len)) continue; - curs->entry = &curs->node->entry; - return curs->node->key; + if (!strncasecmp(curs->node->str, prefix, len)) return curs->node->str; } return NULL; } -const char *cacheSearch(struct Cursor *curs, uint id, const char *substr) { +const char *completeSubstr(struct Cursor *curs, uint id, const char *substr) { if (curs->gen != gen) curs->node = NULL; for ( curs->gen = gen, curs->node = (curs->node ? curs->node->next : head); @@ -178,28 +166,24 @@ const char *cacheSearch(struct Cursor *curs, uint id, const char *substr) { curs->node = curs->node->next ) { if (curs->node->id && curs->node->id != id) continue; - if (!strstr(curs->node->key, substr)) continue; - curs->entry = &curs->node->entry; - return curs->node->key; + if (strstr(curs->node->str, substr)) return curs->node->str; } return NULL; } -const char *cacheNextKey(struct Cursor *curs, uint id) { +const char *completeEach(struct Cursor *curs, uint id) { if (curs->gen != gen) curs->node = NULL; for ( curs->gen = gen, curs->node = (curs->node ? curs->node->next : head); curs->node; curs->node = curs->node->next ) { - if (curs->node->id != id) continue; - curs->entry = &curs->node->entry; - return curs->node->key; + if (curs->node->id == id) return curs->node->str; } return NULL; } -uint cacheNextID(struct Cursor *curs, const char *key) { +uint completeEachID(struct Cursor *curs, const char *str) { if (curs->gen != gen) curs->node = NULL; for ( curs->gen = gen, curs->node = (curs->node ? curs->node->next : head); @@ -207,20 +191,18 @@ uint cacheNextID(struct Cursor *curs, const char *key) { curs->node = curs->node->next ) { if (!curs->node->id) continue; - if (strcmp(curs->node->key, key)) continue; - curs->entry = &curs->node->entry; - return curs->node->id; + if (!strcmp(curs->node->str, str)) return curs->node->id; } return None; } -void cacheAccept(struct Cursor *curs) { +void completeAccept(struct Cursor *curs) { if (curs->gen == gen && curs->node) { prepend(detach(curs->node)); } curs->node = NULL; } -void cacheReject(struct Cursor *curs) { +void completeReject(struct Cursor *curs) { curs->node = NULL; } diff --git a/config.c b/config.c index be88f2f..e568e40 100644 --- a/config.c +++ b/config.c @@ -97,13 +97,6 @@ int getopt_config( } char *equal = &name[len] + strspn(&name[len], WS); - if (*equal && *equal != '=') { - warnx( - "%s:%zu: option `%s' missing equals sign", - path, num, option->name - ); - return clean('?'); - } if (option->has_arg == no_argument && *equal) { warnx( "%s:%zu: option `%s' doesn't allow an argument", @@ -121,8 +114,11 @@ int getopt_config( optarg = NULL; if (*equal) { - char *arg = &equal[1] + strspn(&equal[1], WS); - optarg = strdup(arg); + if (*equal == '=') { + optarg = strdup(&equal[1] + strspn(&equal[1], WS)); + } else { + optarg = strdup(equal); + } if (!optarg) { warn("getopt_config"); return clean('?'); diff --git a/configure b/configure index 9465b77..1fd4ad2 100755 --- a/configure +++ b/configure @@ -29,6 +29,7 @@ for opt; do (--prefix=*) echo "PREFIX = ${opt#*=}" ;; (--bindir=*) echo "BINDIR = ${opt#*=}" ;; (--mandir=*) echo "MANDIR = ${opt#*=}" ;; + (--enable-sandman) echo 'BINS += sandman' ;; (*) echo "warning: unsupported option ${opt}" >&2 ;; esac done @@ -53,6 +54,13 @@ case "$(uname)" in config libtls ncursesw defvar OPENSSL_BIN openssl exec_prefix /bin/openssl ;; + (NetBSD) + cflags "-D'explicit_bzero(b,l)=explicit_memset((b),0,(l))'" + config libtls ncurses + echo 'LDADD.ncursesw = ${LDADD.ncurses}' + echo 'OBJS += compat_readpassphrase.o' + defstr OPENSSL_BIN /usr/bin/openssl + ;; (*) config libtls ncursesw defvar OPENSSL_BIN openssl exec_prefix /bin/openssl diff --git a/filter.c b/filter.c index a7f9a29..bbe40c8 100644 --- a/filter.c +++ b/filter.c @@ -31,7 +31,6 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <sysexits.h> #include "chat.h" @@ -48,14 +47,14 @@ struct Filter filterParse(enum Heat heat, char *pattern) { } struct Filter filterAdd(enum Heat heat, const char *pattern) { - if (len == FilterCap) errx(EX_CONFIG, "filter limit exceeded"); + if (len == FilterCap) errx(1, "filter limit exceeded"); char *own; if (!strchr(pattern, '!') && !strchr(pattern, ' ')) { int n = asprintf(&own, "%s!*@*", pattern); - if (n < 0) err(EX_OSERR, "asprintf"); + if (n < 0) err(1, "asprintf"); } else { own = strdup(pattern); - if (!own) err(EX_OSERR, "strdup"); + if (!own) err(1, "strdup"); } struct Filter filter = filterParse(heat, own); filters[len++] = filter; @@ -105,7 +104,7 @@ static void icedPush(const char *msgID) { size_t i = iced.len % IcedCap; free(iced.msgIDs[i]); iced.msgIDs[i] = strdup(msgID); - if (!iced.msgIDs[i]) err(EX_OSERR, "strdup"); + if (!iced.msgIDs[i]) err(1, "strdup"); iced.len++; } diff --git a/handle.c b/handle.c index bbb3c99..0cc7c04 100644 --- a/handle.c +++ b/handle.c @@ -32,7 +32,6 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <sysexits.h> #include <wchar.h> #include "chat.h" @@ -83,7 +82,7 @@ static void require(struct Message *msg, bool origin, uint len) { } for (uint i = 0; i < len; ++i) { if (msg->params[i]) continue; - errx(EX_PROTOCOL, "%s missing parameter %u", msg->cmd, 1 + i); + errx(1, "%s missing parameter %u", msg->cmd, 1 + i); } } @@ -162,7 +161,7 @@ static void handleErrorNicknameInUse(struct Message *msg) { static void handleErrorErroneousNickname(struct Message *msg) { require(msg, false, 3); if (!strcmp(self.nick, "*")) { - errx(EX_CONFIG, "%s: %s", msg->params[1], msg->params[2]); + errx(1, "%s: %s", msg->params[1], msg->params[2]); } else { handleErrorGeneric(msg); } @@ -193,7 +192,7 @@ static void handleCap(struct Message *msg) { } if (!(self.caps & CapSASL)) ircFormat("CAP END\r\n"); } else if (!strcmp(msg->params[1], "NAK")) { - errx(EX_CONFIG, "server does not support %s", msg->params[2]); + errx(1, "server does not support %s", msg->params[2]); } } @@ -237,7 +236,7 @@ static void handleAuthenticate(struct Message *msg) { size_t userLen = strlen(self.plainUser); size_t passLen = strlen(self.plainPass); size_t len = 1 + userLen + 1 + passLen; - if (sizeof(buf) < len) errx(EX_USAGE, "SASL PLAIN is too long"); + if (sizeof(buf) < len) errx(1, "SASL PLAIN is too long"); memcpy(&buf[1], self.plainUser, userLen); memcpy(&buf[1 + userLen + 1], self.plainPass, passLen); @@ -260,13 +259,13 @@ static void handleReplyLoggedIn(struct Message *msg) { static void handleErrorSASLFail(struct Message *msg) { require(msg, false, 2); - errx(EX_CONFIG, "%s", msg->params[1]); + errx(1, "%s", msg->params[1]); } static void handleReplyWelcome(struct Message *msg) { require(msg, false, 1); set(&self.nick, msg->params[0]); - cacheInsert(true, Network, self.nick); + completePull(Network, self.nick, Default); if (self.mode) ircFormat("MODE %s %s\r\n", self.nick, self.mode); if (self.join) { uint count = 1; @@ -278,7 +277,7 @@ static void handleReplyWelcome(struct Message *msg) { replies[ReplyTopicAuto] += count; replies[ReplyNamesAuto] += count; } - commandCache(); + commandCompletion(); handleReplyGeneric(msg); } @@ -315,7 +314,7 @@ static void handleReplyISupport(struct Message *msg) { char *modes = strsep(&msg->params[i], ")"); char *prefixes = msg->params[i]; if (!modes || !prefixes || strlen(modes) != strlen(prefixes)) { - errx(EX_PROTOCOL, "invalid PREFIX value"); + errx(1, "invalid PREFIX value"); } set(&network.prefixModes, modes); set(&network.prefixes, prefixes); @@ -325,7 +324,7 @@ static void handleReplyISupport(struct Message *msg) { char *setParam = strsep(&msg->params[i], ","); char *channel = strsep(&msg->params[i], ","); if (!list || !param || !setParam || !channel) { - errx(EX_PROTOCOL, "invalid CHANMODES value"); + errx(1, "invalid CHANMODES value"); } set(&network.listModes, list); set(&network.paramModes, param); @@ -372,13 +371,13 @@ static void handleJoin(struct Message *msg) { set(&self.host, msg->host); } idColors[id] = hash(msg->params[0]); - cacheInsert(true, None, msg->params[0])->color = idColors[id]; + completePull(None, msg->params[0], idColors[id]); if (replies[ReplyJoin]) { windowShow(windowFor(id)); replies[ReplyJoin]--; } } - cacheInsert(true, id, msg->nick)->color = hash(msg->user); + completePull(id, msg->nick, hash(msg->user)); if (msg->params[2] && !strcasecmp(msg->params[2], msg->nick)) { msg->params[2] = NULL; } @@ -410,9 +409,9 @@ static void handlePart(struct Message *msg) { require(msg, true, 1); uint id = idFor(msg->params[0]); if (!strcmp(msg->nick, self.nick)) { - cacheClear(id); + completeRemove(id, NULL); } - cacheRemove(id, msg->nick); + completeRemove(id, msg->nick); enum Heat heat = filterCheck(Cold, id, msg); if (heat > Ice) urlScan(id, msg->nick, msg->params[1]); uiFormat( @@ -432,14 +431,14 @@ static void handleKick(struct Message *msg) { require(msg, true, 2); uint id = idFor(msg->params[0]); bool kicked = !strcmp(msg->params[1], self.nick); - cacheInsert(true, id, msg->nick)->color = hash(msg->user); + completePull(id, msg->nick, hash(msg->user)); urlScan(id, msg->nick, msg->params[2]); uiFormat( id, (kicked ? Hot : Cold), tagTime(msg), "%s\3%02d%s\17\tkicks \3%02d%s\3 out of \3%02d%s\3%s%s", (kicked ? "\26" : ""), hash(msg->user), msg->nick, - cacheGet(id, msg->params[1])->color, msg->params[1], + completeColor(id, msg->params[1]), msg->params[1], hash(msg->params[0]), msg->params[0], (msg->params[2] ? ": " : ""), (msg->params[2] ?: "") ); @@ -448,8 +447,8 @@ static void handleKick(struct Message *msg) { msg->nick, msg->params[1], msg->params[0], (msg->params[2] ? ": " : ""), (msg->params[2] ?: "") ); - cacheRemove(id, msg->params[1]); - if (kicked) cacheClear(id); + completeRemove(id, msg->params[1]); + if (kicked) completeRemove(id, NULL); } static void handleNick(struct Message *msg) { @@ -459,7 +458,7 @@ static void handleNick(struct Message *msg) { inputUpdate(); } struct Cursor curs = {0}; - for (uint id; (id = cacheNextID(&curs, msg->nick));) { + for (uint id; (id = completeEachID(&curs, msg->nick));) { if (!strcmp(idNames[id], msg->nick)) { set(&idNames[id], msg->params[0]); } @@ -474,13 +473,13 @@ static void handleNick(struct Message *msg) { msg->nick, msg->params[0] ); } - cacheReplace(true, msg->nick, msg->params[0]); + completeReplace(msg->nick, msg->params[0]); } static void handleSetname(struct Message *msg) { require(msg, true, 1); struct Cursor curs = {0}; - for (uint id; (id = cacheNextID(&curs, msg->nick));) { + for (uint id; (id = completeEachID(&curs, msg->nick));) { uiFormat( id, filterCheck(Cold, id, msg), tagTime(msg), "\3%02d%s\3\tis now known as \3%02d%s\3 (%s\17)", @@ -493,7 +492,7 @@ static void handleSetname(struct Message *msg) { static void handleQuit(struct Message *msg) { require(msg, true, 0); struct Cursor curs = {0}; - for (uint id; (id = cacheNextID(&curs, msg->nick));) { + for (uint id; (id = completeEachID(&curs, msg->nick));) { enum Heat heat = filterCheck(Cold, id, msg); if (heat > Ice) urlScan(id, msg->nick, msg->params[0]); uiFormat( @@ -509,7 +508,7 @@ static void handleQuit(struct Message *msg) { (msg->params[0] ? ": " : ""), (msg->params[0] ?: "") ); } - cacheRemove(None, msg->nick); + completeRemove(None, msg->nick); } static void handleInvite(struct Message *msg) { @@ -555,7 +554,7 @@ static void handleErrorUserOnChannel(struct Message *msg) { uiFormat( id, Warm, tagTime(msg), "\3%02d%s\3 is already in \3%02d%s\3", - cacheGet(id, msg->params[1])->color, msg->params[1], + completeColor(id, msg->params[1]), msg->params[1], hash(msg->params[2]), msg->params[2] ); } @@ -575,9 +574,8 @@ static void handleReplyNames(struct Message *msg) { for (char *p = prefixes; p < nick; ++p) { bits |= prefixBit(*p); } - struct Entry *entry = cacheInsert(false, id, nick); - if (user) entry->color = color; - entry->prefixBits = bits; + completePush(id, nick, color); + *completeBits(id, nick) = bits; if (!replies[ReplyNames] && !replies[ReplyNamesAuto]) continue; ptr = seprintf( ptr, end, "%s\3%02d%s\3", (ptr > buf ? ", " : ""), color, prefixes @@ -609,24 +607,24 @@ static void handleReplyNoTopic(struct Message *msg) { ); } -static void topicCache(uint id, const char *topic) { +static void topicComplete(uint id, const char *topic) { char buf[512]; struct Cursor curs = {0}; - const char *prev = cacheComplete(&curs, id, "/topic "); + const char *prev = completePrefix(&curs, id, "/topic "); if (prev) { snprintf(buf, sizeof(buf), "%s", prev); - cacheRemove(id, buf); + completeRemove(id, buf); } if (topic) { snprintf(buf, sizeof(buf), "/topic %s", topic); - cacheInsert(false, id, buf); + completePush(id, buf, Default); } } static void handleReplyTopic(struct Message *msg) { require(msg, false, 3); uint id = idFor(msg->params[1]); - topicCache(id, msg->params[2]); + topicComplete(id, msg->params[2]); if (!replies[ReplyTopic] && !replies[ReplyTopicAuto]) return; urlScan(id, NULL, msg->params[2]); uiFormat( @@ -677,7 +675,7 @@ static void handleTopic(struct Message *msg) { require(msg, true, 2); uint id = idFor(msg->params[0]); if (!msg->params[1][0]) { - topicCache(id, NULL); + topicComplete(id, NULL); uiFormat( id, Warm, tagTime(msg), "\3%02d%s\3\tremoves the sign in \3%02d%s\3", @@ -691,7 +689,7 @@ static void handleTopic(struct Message *msg) { } struct Cursor curs = {0}; - const char *prev = cacheComplete(&curs, id, "/topic "); + const char *prev = completePrefix(&curs, id, "/topic "); if (prev) { prev += 7; } else { @@ -741,7 +739,7 @@ log: id, tagTime(msg), "%s places a new sign in %s: %s", msg->nick, msg->params[0], msg->params[1] ); - topicCache(id, msg->params[1]); + topicComplete(id, msg->params[1]); urlScan(id, msg->nick, msg->params[1]); } @@ -858,22 +856,23 @@ static void handleMode(struct Message *msg) { if (strchr(network.prefixModes, *ch)) { if (i >= ParamCap || !msg->params[i]) { - errx(EX_PROTOCOL, "MODE missing %s parameter", mode); + errx(1, "MODE missing %s parameter", mode); } char *nick = msg->params[i++]; char prefix = network.prefixes[ strchr(network.prefixModes, *ch) - network.prefixModes ]; + completePush(id, nick, Default); if (set) { - cacheInsert(false, id, nick)->prefixBits |= prefixBit(prefix); + *completeBits(id, nick) |= prefixBit(prefix); } else { - cacheInsert(false, id, nick)->prefixBits &= ~prefixBit(prefix); + *completeBits(id, nick) &= ~prefixBit(prefix); } uiFormat( id, Cold, tagTime(msg), "\3%02d%s\3\t%s \3%02d%c%s\3 %s%s in \3%02d%s\3", hash(msg->user), msg->nick, verb, - cacheGet(id, nick)->color, prefix, nick, + completeColor(id, nick), prefix, nick, mode, name, hash(msg->params[0]), msg->params[0] ); logFormat( @@ -884,7 +883,7 @@ static void handleMode(struct Message *msg) { if (strchr(network.listModes, *ch)) { if (i >= ParamCap || !msg->params[i]) { - errx(EX_PROTOCOL, "MODE missing %s parameter", mode); + errx(1, "MODE missing %s parameter", mode); } char *mask = msg->params[i++]; if (*ch == 'b') { @@ -917,7 +916,7 @@ static void handleMode(struct Message *msg) { if (strchr(network.paramModes, *ch)) { if (i >= ParamCap || !msg->params[i]) { - errx(EX_PROTOCOL, "MODE missing %s parameter", mode); + errx(1, "MODE missing %s parameter", mode); } char *param = msg->params[i++]; uiFormat( @@ -934,7 +933,7 @@ static void handleMode(struct Message *msg) { if (strchr(network.setParamModes, *ch) && set) { if (i >= ParamCap || !msg->params[i]) { - errx(EX_PROTOCOL, "MODE missing %s parameter", mode); + errx(1, "MODE missing %s parameter", mode); } char *param = msg->params[i++]; uiFormat( @@ -1011,7 +1010,7 @@ static void handleReplyBanList(struct Message *msg) { id, Warm, tagTime(msg), "Banned from \3%02d%s\3 since %s by \3%02d%s\3: %s", hash(msg->params[1]), msg->params[1], - since, cacheGet(id, msg->params[3])->color, msg->params[3], + since, completeColor(id, msg->params[3]), msg->params[3], msg->params[2] ); } else { @@ -1034,7 +1033,7 @@ static void onList(const char *list, struct Message *msg) { id, Warm, tagTime(msg), "On the \3%02d%s\3 %s list since %s by \3%02d%s\3: %s", hash(msg->params[1]), msg->params[1], list, - since, cacheGet(id, msg->params[3])->color, msg->params[3], + since, completeColor(id, msg->params[3]), msg->params[3], msg->params[2] ); } else { @@ -1055,19 +1054,19 @@ static void handleReplyInviteList(struct Message *msg) { } static void handleReplyList(struct Message *msg) { - require(msg, false, 4); + require(msg, false, 3); uiFormat( Network, Warm, tagTime(msg), "In \3%02d%s\3 are %ld under the banner: %s", hash(msg->params[1]), msg->params[1], strtol(msg->params[2], NULL, 10), - msg->params[3] + (msg->params[3] ?: "") ); } static void handleReplyWhoisUser(struct Message *msg) { require(msg, false, 6); - cacheInsert(true, Network, msg->params[1])->color = hash(msg->params[2]); + completePull(Network, msg->params[1], hash(msg->params[2])); uiFormat( Network, Warm, tagTime(msg), "\3%02d%s\3\tis %s!%s@%s (%s\17)", @@ -1082,7 +1081,7 @@ static void handleReplyWhoisServer(struct Message *msg) { uiFormat( Network, Warm, tagTime(msg), "\3%02d%s\3\t%s connected to %s (%s)", - cacheGet(Network, msg->params[1])->color, msg->params[1], + completeColor(Network, msg->params[1]), msg->params[1], (replies[ReplyWhowas] ? "was" : "is"), msg->params[2], msg->params[3] ); } @@ -1106,7 +1105,7 @@ static void handleReplyWhoisIdle(struct Message *msg) { uiFormat( Network, Warm, tagTime(msg), "\3%02d%s\3\tis idle for %lu %s%s%s%s", - cacheGet(Network, msg->params[1])->color, msg->params[1], + completeColor(Network, msg->params[1]), msg->params[1], idle, unit, (idle != 1 ? "s" : ""), (msg->params[3] ? ", signed on " : ""), (msg->params[3] ? signon : "") ); @@ -1128,7 +1127,7 @@ static void handleReplyWhoisChannels(struct Message *msg) { uiFormat( Network, Warm, tagTime(msg), "\3%02d%s\3\tis in %s", - cacheGet(Network, msg->params[1])->color, msg->params[1], buf + completeColor(Network, msg->params[1]), msg->params[1], buf ); } @@ -1142,7 +1141,7 @@ static void handleReplyWhoisGeneric(struct Message *msg) { uiFormat( Network, Warm, tagTime(msg), "\3%02d%s\3\t%s%s%s", - cacheGet(Network, msg->params[1])->color, msg->params[1], + completeColor(Network, msg->params[1]), msg->params[1], msg->params[2], (msg->params[3] ? " " : ""), (msg->params[3] ?: "") ); } @@ -1150,13 +1149,13 @@ static void handleReplyWhoisGeneric(struct Message *msg) { static void handleReplyEndOfWhois(struct Message *msg) { require(msg, false, 2); if (strcmp(msg->params[1], self.nick)) { - cacheRemove(Network, msg->params[1]); + completeRemove(Network, msg->params[1]); } } static void handleReplyWhowasUser(struct Message *msg) { require(msg, false, 6); - cacheInsert(true, Network, msg->params[1])->color = hash(msg->params[2]); + completePull(Network, msg->params[1], hash(msg->params[2])); uiFormat( Network, Warm, tagTime(msg), "\3%02d%s\3\twas %s!%s@%s (%s)", @@ -1168,7 +1167,7 @@ static void handleReplyWhowasUser(struct Message *msg) { static void handleReplyEndOfWhowas(struct Message *msg) { require(msg, false, 2); if (strcmp(msg->params[1], self.nick)) { - cacheRemove(Network, msg->params[1]); + completeRemove(Network, msg->params[1]); } } @@ -1179,7 +1178,7 @@ static void handleReplyAway(struct Message *msg) { uiFormat( id, (id == Network ? Warm : Cold), tagTime(msg), "\3%02d%s\3\tis away: %s", - cacheGet(id, msg->params[1])->color, msg->params[1], msg->params[2] + completeColor(id, msg->params[1]), msg->params[1], msg->params[2] ); logFormat( id, tagTime(msg), "%s is away: %s", @@ -1250,7 +1249,7 @@ static char *colorMentions(char *ptr, char *end, uint id, const char *msg) { size_t len = strcspn(msg, ",:<> "); char *p = seprintf(ptr, end, "%.*s", (int)len, msg); - enum Color color = cacheGet(id, ptr)->color; + enum Color color = completeColor(id, ptr); if (color != Default) { ptr = seprintf(ptr, end, "\3%02d%.*s\3", color, (int)len, msg); } else { @@ -1290,7 +1289,7 @@ static void handlePrivmsg(struct Message *msg) { heat = filterCheck(heat, id, msg); if (heat > Warm && !mine && !query) highlight = true; if (!notice && !mine && heat > Ice) { - cacheInsert(true, id, msg->nick)->color = hash(msg->user); + completePull(id, msg->nick, hash(msg->user)); } if (heat > Ice) urlScan(id, msg->nick, msg->params[1]); @@ -1337,7 +1336,7 @@ static void handlePing(struct Message *msg) { static void handleError(struct Message *msg) { require(msg, false, 1); - errx(EX_UNAVAILABLE, "%s", msg->params[0]); + errx(69, "%s", msg->params[0]); } static const struct Handler { diff --git a/input.c b/input.c index bcefee5..7e1f9c1 100644 --- a/input.c +++ b/input.c @@ -35,7 +35,6 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <sysexits.h> #include <termios.h> #include <unistd.h> #include <wchar.h> @@ -100,7 +99,7 @@ void inputInit(void) { struct termios term; int error = tcgetattr(STDOUT_FILENO, &term); - if (error) err(EX_OSERR, "tcgetattr"); + if (error) err(1, "tcgetattr"); // Gain use of C-q, C-s, C-c, C-z, C-y, C-v, C-o. term.c_iflag &= ~IXON; @@ -113,7 +112,7 @@ void inputInit(void) { term.c_cc[VDISCARD] = _POSIX_VDISABLE; error = tcsetattr(STDOUT_FILENO, TCSANOW, &term); - if (error) err(EX_OSERR, "tcsetattr"); + if (error) err(1, "tcsetattr"); def_prog_mode(); @@ -172,7 +171,7 @@ void inputUpdate(void) { size_t pos = 0; const char *ptr = editString(&edits[id], &buf, &cap, &pos); - if (!ptr) err(EX_OSERR, "editString"); + if (!ptr) err(1, "editString"); const char *prefix = ""; const char *prompt = self.nick; @@ -261,12 +260,12 @@ static const struct { { L"\\wave", L"ヾ(^∇^)" }, }; -void inputCache(void) { +void inputCompletion(void) { char mbs[256]; for (size_t i = 0; i < ARRAY_LEN(Macros); ++i) { size_t n = wcstombs(mbs, Macros[i].name, sizeof(mbs)); assert(n != (size_t)-1); - cacheInsert(false, None, mbs); + completePush(None, mbs, Default); } } @@ -300,12 +299,12 @@ static struct { } tab; static void tabAccept(void) { - cacheAccept(&tab.curs); + completeAccept(&tab.curs); tab.len = 0; } static void tabReject(void) { - cacheReject(&tab.curs); + completeReject(&tab.curs); tab.len = 0; } @@ -333,9 +332,9 @@ static int tabComplete(struct Edit *e, uint id) { tab.suffix = true; } - const char *comp = cacheComplete(&tab.curs, id, tab.pre); + const char *comp = completePrefix(&tab.curs, id, tab.pre); if (!comp) { - comp = cacheComplete(&tab.curs, id, tab.pre); + comp = completePrefix(&tab.curs, id, tab.pre); tab.suffix ^= true; } if (!comp) { @@ -394,7 +393,7 @@ fail: static void inputEnter(void) { uint id = windowID(); char *cmd = editString(&edits[id], &buf, &cap, NULL); - if (!cmd) err(EX_OSERR, "editString"); + if (!cmd) err(1, "editString"); tabAccept(); editFn(&edits[id], EditClear); @@ -450,7 +449,7 @@ static void keyCode(int code) { break; case KEY_SHOME: windowScroll(ScrollAll, +1); break; case KEY_UP: windowScroll(ScrollOne, +1); } - if (error) err(EX_OSERR, "editFn"); + if (error) err(1, "editFn"); } static void keyCtrl(wchar_t ch) { @@ -480,7 +479,7 @@ static void keyCtrl(wchar_t ch) { break; case L'X': error = macroExpand(edit); tabAccept(); break; case L'Y': error = editFn(edit, EditPaste); } - if (error) err(EX_OSERR, "editFn"); + if (error) err(1, "editFn"); } static void keyStyle(wchar_t ch) { @@ -516,7 +515,7 @@ static void keyStyle(wchar_t ch) { struct Edit *edit = &edits[windowID()]; for (char *ch = buf; *ch; ++ch) { int error = editInsert(edit, *ch); - if (error) err(EX_OSERR, "editInsert"); + if (error) err(1, "editInsert"); } } @@ -552,7 +551,7 @@ void inputRead(void) { paste ^= true; } else if (paste || literal) { int error = editInsert(&edits[windowID()], ch); - if (error) err(EX_OSERR, "editInsert"); + if (error) err(1, "editInsert"); } else if (ret == KEY_CODE_YES) { keyCode(ch); } else if (ch == (L'Z' ^ L'@')) { @@ -568,7 +567,7 @@ void inputRead(void) { keyCtrl(ch); } else { int error = editInsert(&edits[windowID()], ch); - if (error) err(EX_OSERR, "editInsert"); + if (error) err(1, "editInsert"); } style = false; literal = false; @@ -609,7 +608,7 @@ int inputSave(FILE *file) { static ssize_t 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"); + if (len < 0 && !feof(file)) err(1, "getdelim"); return len; } @@ -620,7 +619,7 @@ void inputLoad(FILE *file, size_t version) { readString(file, &buf, &cap); size_t max = strlen(buf); int error = editReserve(&edits[id], 0, max); - if (error) err(EX_OSERR, "editReserve"); + if (error) err(1, "editReserve"); size_t len = mbstowcs(edits[id].buf, buf, max); assert(len != (size_t)-1); edits[id].len = len; diff --git a/irc.c b/irc.c index 1fc2c3f..28e557b 100644 --- a/irc.c +++ b/irc.c @@ -38,7 +38,6 @@ #include <string.h> #include <sys/socket.h> #include <sys/stat.h> -#include <sysexits.h> #include <tls.h> #include <unistd.h> @@ -54,7 +53,7 @@ void ircConfig( char buf[PATH_MAX]; config = tls_config_new(); - if (!config) errx(EX_SOFTWARE, "tls_config_new"); + if (!config) errx(1, "tls_config_new"); if (insecure) { tls_config_insecure_noverifycert(config); @@ -66,7 +65,7 @@ void ircConfig( error = tls_config_set_ca_file(config, buf); if (!error) break; } - if (error) errx(EX_NOINPUT, "%s: %s", trust, tls_config_error(config)); + if (error) errx(1, "%s: %s", trust, tls_config_error(config)); } // Explicitly load the default CA cert file on OpenBSD now so it doesn't @@ -76,7 +75,7 @@ void ircConfig( if (!insecure && !trust) { const char *ca = tls_default_ca_cert_file(); error = tls_config_set_ca_file(config, ca); - if (error) errx(EX_OSFILE, "%s: %s", ca, tls_config_error(config)); + if (error) errx(1, "%s: %s", ca, tls_config_error(config)); } #endif @@ -89,21 +88,21 @@ void ircConfig( } if (!error) break; } - if (error) errx(EX_NOINPUT, "%s: %s", cert, tls_config_error(config)); + if (error) errx(1, "%s: %s", cert, tls_config_error(config)); } if (priv) { for (int i = 0; configPath(buf, sizeof(buf), priv, i); ++i) { error = tls_config_set_key_file(config, buf); if (!error) break; } - if (error) errx(EX_NOINPUT, "%s: %s", priv, tls_config_error(config)); + if (error) errx(1, "%s: %s", priv, tls_config_error(config)); } client = tls_client(); - if (!client) errx(EX_SOFTWARE, "tls_client"); + if (!client) errx(1, "tls_client"); error = tls_configure(client, config); - if (error) errx(EX_SOFTWARE, "tls_configure: %s", tls_error(client)); + if (error) errx(1, "tls_configure: %s", tls_error(client)); } int ircConnect(const char *bindHost, const char *host, const char *port) { @@ -120,11 +119,11 @@ int ircConnect(const char *bindHost, const char *host, const char *port) { if (bindHost) { error = getaddrinfo(bindHost, NULL, &hints, &head); - if (error) errx(EX_NOHOST, "%s: %s", bindHost, gai_strerror(error)); + if (error) errx(1, "%s: %s", bindHost, gai_strerror(error)); for (struct addrinfo *ai = head; ai; ai = ai->ai_next) { sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); - if (sock < 0) err(EX_OSERR, "socket"); + if (sock < 0) err(1, "socket"); error = bind(sock, ai->ai_addr, ai->ai_addrlen); if (!error) { @@ -135,17 +134,17 @@ int ircConnect(const char *bindHost, const char *host, const char *port) { close(sock); sock = -1; } - if (sock < 0) err(EX_UNAVAILABLE, "%s", bindHost); + if (sock < 0) err(1, "%s", bindHost); freeaddrinfo(head); } error = getaddrinfo(host, port, &hints, &head); - if (error) errx(EX_NOHOST, "%s:%s: %s", host, port, gai_strerror(error)); + if (error) errx(1, "%s:%s: %s", host, port, gai_strerror(error)); for (struct addrinfo *ai = head; ai; ai = ai->ai_next) { if (sock < 0) { sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); - if (sock < 0) err(EX_OSERR, "socket"); + if (sock < 0) err(1, "socket"); } error = connect(sock, ai->ai_addr, ai->ai_addrlen); @@ -155,12 +154,12 @@ int ircConnect(const char *bindHost, const char *host, const char *port) { close(sock); sock = -1; } - if (sock < 0) err(EX_UNAVAILABLE, "%s:%s", host, port); + if (sock < 0) err(69, "%s:%s", host, port); freeaddrinfo(head); fcntl(sock, F_SETFD, FD_CLOEXEC); error = tls_connect_socket(client, sock, host); - if (error) errx(EX_PROTOCOL, "tls_connect: %s", tls_error(client)); + if (error) errx(1, "tls_connect: %s", tls_error(client)); return sock; } @@ -170,7 +169,7 @@ void ircHandshake(void) { do { error = tls_handshake(client); } while (error == TLS_WANT_POLLIN || error == TLS_WANT_POLLOUT); - if (error) errx(EX_PROTOCOL, "tls_handshake: %s", tls_error(client)); + if (error) errx(1, "tls_handshake: %s", tls_error(client)); tls_config_clear_keys(config); } @@ -202,7 +201,7 @@ void ircSend(const char *ptr, size_t len) { while (len) { ssize_t ret = tls_write(client, ptr, len); if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT) continue; - if (ret < 0) errx(EX_IOERR, "tls_write: %s", tls_error(client)); + if (ret < 0) errx(1, "tls_write: %s", tls_error(client)); ptr += ret; len -= ret; } @@ -287,8 +286,8 @@ void ircRecv(void) { assert(client); ssize_t ret = tls_read(client, &buf[len], sizeof(buf) - len); if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT) return; - if (ret < 0) errx(EX_IOERR, "tls_read: %s", tls_error(client)); - if (!ret) errx(EX_PROTOCOL, "server closed connection"); + if (ret < 0) errx(1, "tls_read: %s", tls_error(client)); + if (!ret) errx(69, "server closed connection"); len += ret; char *crlf; diff --git a/log.c b/log.c index d6b3f2a..181c009 100644 --- a/log.c +++ b/log.c @@ -34,7 +34,6 @@ #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> -#include <sysexits.h> #include <time.h> #include <unistd.h> @@ -49,13 +48,13 @@ static int logDir = -1; 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); + if (error && errno != EEXIST) err(1, "%s", buf); error = mkdir(dataPath(buf, sizeof(buf), "log", 0), S_IRWXU); - if (error && errno != EEXIST) err(EX_CANTCREAT, "%s", buf); + if (error && errno != EEXIST) err(1, "%s", buf); logDir = open(buf, O_RDONLY | O_CLOEXEC); - if (logDir < 0) err(EX_CANTCREAT, "%s", buf); + if (logDir < 0) err(1, "%s", buf); #ifdef __FreeBSD__ cap_rights_t rights; @@ -64,13 +63,13 @@ void logOpen(void) { /* for fdopen(3) */ CAP_FCNTL, CAP_FSTAT ); error = caph_rights_limit(logDir, &rights); - if (error) err(EX_OSERR, "cap_rights_limit"); + if (error) err(1, "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); + if (error && errno != EEXIST) err(1, "log/%s", path); } static void sanitize(char *ptr, char *end) { @@ -99,7 +98,7 @@ static FILE *logFile(uint id, const struct tm *tm) { if (logs[id].file) { int error = fclose(logs[id].file); - if (error) err(EX_IOERR, "%s", idNames[id]); + if (error) err(1, "%s", idNames[id]); } logs[id].year = tm->tm_year; @@ -119,16 +118,16 @@ static FILE *logFile(uint id, const struct tm *tm) { logMkdir(path); size_t len = strftime(ptr, end - ptr, "/%F.log", tm); - if (!len) errx(EX_CANTCREAT, "log path too long"); + if (!len) errx(1, "log path too long"); int fd = openat( logDir, path, O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR ); - if (fd < 0) err(EX_CANTCREAT, "log/%s", path); + if (fd < 0) err(1, "log/%s", path); logs[id].file = fdopen(fd, "a"); - if (!logs[id].file) err(EX_OSERR, "fdopen"); + if (!logs[id].file) err(1, "fdopen"); setlinebuf(logs[id].file); return logs[id].file; @@ -139,7 +138,7 @@ void logClose(void) { 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]); + if (error) err(1, "%s", idNames[id]); } close(logDir); } @@ -149,21 +148,21 @@ void logFormat(uint id, const time_t *src, const char *format, ...) { time_t ts = (src ? *src : time(NULL)); struct tm *tm = localtime(&ts); - if (!tm) err(EX_OSERR, "localtime"); + if (!tm) err(1, "localtime"); 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]); + if (n < 0) err(1, "%s", idNames[id]); va_list ap; va_start(ap, format); n = vfprintf(file, format, ap); va_end(ap); - if (n < 0) err(EX_IOERR, "%s", idNames[id]); + if (n < 0) err(1, "%s", idNames[id]); n = fprintf(file, "\n"); - if (n < 0) err(EX_IOERR, "%s", idNames[id]); + if (n < 0) err(1, "%s", idNames[id]); } diff --git a/scripts/sandman.1 b/sandman.1 index 92828c0..92828c0 100644 --- a/scripts/sandman.1 +++ b/sandman.1 diff --git a/scripts/sandman.m b/sandman.m index 2e5c4db..c9d0705 100644 --- a/scripts/sandman.m +++ b/sandman.m @@ -19,7 +19,6 @@ #import <signal.h> #import <stdio.h> #import <stdlib.h> -#import <sysexits.h> #import <unistd.h> typedef unsigned uint; @@ -27,17 +26,17 @@ typedef unsigned uint; static pid_t pid; static void spawn(char *argv[]) { pid = fork(); - if (pid < 0) err(EX_OSERR, "fork"); + if (pid < 0) err(1, "fork"); if (pid) return; execvp(argv[0], argv); - err(EX_CONFIG, "%s", argv[0]); + err(127, "%s", argv[0]); } static void handler(int signal) { (void)signal; int status; pid_t pid = wait(&status); - if (pid < 0) _exit(EX_OSERR); + if (pid < 0) _exit(1); _exit(status); } @@ -47,12 +46,12 @@ int main(int argc, char *argv[]) { for (int opt; 0 < (opt = getopt(argc, argv, "t:"));) { switch (opt) { break; case 't': delay = strtoul(optarg, NULL, 10); - break; default: return EX_USAGE; + break; default: return 1; } } argc -= optind; argv += optind; - if (!argc) errx(EX_USAGE, "command required"); + if (!argc) errx(1, "command required"); NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; NSNotificationCenter *notifCenter = [workspace notificationCenter]; @@ -64,7 +63,7 @@ int main(int argc, char *argv[]) { (void)notif; signal(SIGCHLD, SIG_IGN); int error = kill(pid, SIGHUP); - if (error) err(EX_UNAVAILABLE, "kill"); + if (error) err(1, "kill"); int status; wait(&status); }]; diff --git a/scripts/.gitignore b/scripts/.gitignore deleted file mode 100644 index f6dc107..0000000 --- a/scripts/.gitignore +++ /dev/null @@ -1 +0,0 @@ -sandman diff --git a/scripts/Makefile b/scripts/Makefile deleted file mode 100644 index 179a2d3..0000000 --- a/scripts/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -PREFIX ?= /usr/local -BINDIR ?= ${PREFIX}/bin -MANDIR ?= ${PREFIX}/man - -CFLAGS += -Wall -Wextra - --include ../config.mk - -LDLIBS = -framework Cocoa - -all: sandman - -clean: - rm -f sandman - -install: sandman sandman.1 - install -d ${DESTDIR}${BINDIR} ${DESTDIR}${MANDIR}/man1 - install sandman ${DESTDIR}${BINDIR} - install -m 644 sandman.1 ${DESTDIR}${MANDIR}/man1 - -uninstall: - rm -f ${DESTDIR}${BINDIR}/sandman ${DESTDIR}/man/man1/sandman.1 diff --git a/scripts/build-chroot.sh b/scripts/build-chroot.sh deleted file mode 100644 index a0fcf32..0000000 --- a/scripts/build-chroot.sh +++ /dev/null @@ -1,74 +0,0 @@ -#!/bin/sh -set -eux - -CHROOT_USER=$1 -CHROOT_GROUP=$2 - -if [ "$(uname)" = 'OpenBSD' ]; then - install -d -o root -g wheel \ - root \ - root/bin \ - root/etc/ssl \ - root/home \ - root/usr/bin \ - root/usr/lib \ - root/usr/libexec \ - root/usr/share/man - install -d -o ${CHROOT_USER} -g ${CHROOT_GROUP} \ - root/home/${CHROOT_USER} \ - root/home/${CHROOT_USER}/.local/share - - cp -fp /bin/sh root/bin - cp -fp /usr/libexec/ld.so root/usr/libexec - export LD_TRACE_LOADED_OBJECTS_FMT1='%p\n' - export LD_TRACE_LOADED_OBJECTS_FMT2='' - for bin in ./catgirl /usr/bin/mandoc /usr/bin/less; do - LD_TRACE_LOADED_OBJECTS=1 $bin | xargs -t -J % cp -fp % root/usr/lib - done - cp -fp /usr/bin/printf /usr/bin/mandoc /usr/bin/less root/usr/bin - make install DESTDIR=root PREFIX=/usr MANDIR=/usr/share/man - install scripts/chroot-prompt.sh root/usr/bin/catgirl-prompt - install scripts/chroot-man.sh root/usr/bin/man - - cp -fp /etc/hosts /etc/resolv.conf root/etc - cp -fp /etc/ssl/cert.pem root/etc/ssl - cp -af /usr/share/locale /usr/share/terminfo root/usr/share - - tar -c -f chroot.tar -C root bin etc home usr - -elif [ "$(uname)" = 'FreeBSD' ]; then - install -d -o root -g wheel \ - root \ - root/bin \ - root/etc \ - root/home \ - root/lib \ - root/libexec \ - root/usr/bin \ - root/usr/local/etc/ssl \ - root/usr/share/man \ - root/usr/share/misc - install -d -o ${CHROOT_USER} -g ${CHROOT_GROUP} \ - root/home/${CHROOT_USER} \ - root/home/${CHROOT_USER}/.local/share - - cp -fp /libexec/ld-elf.so.1 root/libexec - ldd -f '%p\n' catgirl /usr/bin/mandoc /usr/bin/less \ - | sort -u | xargs -t -J % cp -fp % root/lib - chflags noschg root/libexec/* root/lib/* - cp -fp /rescue/sh /usr/bin/mandoc /usr/bin/less root/bin - make install DESTDIR=root PREFIX=/usr MANDIR=/usr/share/man - install scripts/chroot-prompt.sh root/usr/bin/catgirl-prompt - install scripts/chroot-man.sh root/usr/bin/man - - cp -fp /etc/hosts /etc/resolv.conf root/etc - cp -fp /usr/local/etc/ssl/cert.pem root/usr/local/etc/ssl - cp -af /usr/share/locale root/usr/share - cp -fp /usr/share/misc/termcap.db root/usr/share/misc - - tar -c -f chroot.tar -C root bin etc home lib libexec usr - -else - echo "Don't know how to build chroot on $(uname)" >&2 - exit 1 -fi diff --git a/scripts/chroot-man.sh b/scripts/chroot-man.sh deleted file mode 100644 index 9d686f9..0000000 --- a/scripts/chroot-man.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -exec mandoc /usr/share/man/man1/catgirl.1 | LESSSECURE=1 less diff --git a/scripts/chroot-prompt.sh b/scripts/chroot-prompt.sh deleted file mode 100644 index 2b34426..0000000 --- a/scripts/chroot-prompt.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh -set -eu - -printf 'Name: ' -read -r nick rest -printf '%s %s\n' "$nick" "$SSH_CLIENT" >>nicks.log -exec catgirl -K -n "$nick" -s "${nick##*/}" -u "${SSH_CLIENT%% *}" "$@" diff --git a/scripts/reconnect.sh b/scripts/reconnect.sh new file mode 100644 index 0000000..92d9668 --- /dev/null +++ b/scripts/reconnect.sh @@ -0,0 +1,10 @@ +#!/bin/sh +set -u + +while :; do + catgirl "$@" + status=$? + if [ $status -ne 69 ]; then + exit $status + fi +done diff --git a/scripts/sshd_config b/scripts/sshd_config deleted file mode 100644 index c7e99ec..0000000 --- a/scripts/sshd_config +++ /dev/null @@ -1,9 +0,0 @@ -UsePAM no - -Match User chat - PasswordAuthentication yes - PermitEmptyPasswords yes - ChrootDirectory /home/chat - ForceCommand catgirl-prompt - DisableForwarding yes - MaxSessions 1 diff --git a/ui.c b/ui.c index 7728402..df675b1 100644 --- a/ui.c +++ b/ui.c @@ -39,7 +39,6 @@ #include <stdlib.h> #include <string.h> #include <sys/file.h> -#include <sysexits.h> #include <term.h> #include <time.h> #include <unistd.h> @@ -122,13 +121,13 @@ void uiInit(void) { } uiStatus = newwin(StatusLines, COLS, 0, 0); - if (!uiStatus) err(EX_OSERR, "newwin"); + if (!uiStatus) err(1, "newwin"); uiMain = newwin(MAIN_LINES, COLS, StatusLines, 0); - if (!uiMain) err(EX_OSERR, "newwin"); + if (!uiMain) err(1, "newwin"); uiInput = newpad(InputLines, InputCols); - if (!uiInput) err(EX_OSERR, "newpad"); + if (!uiInput) err(1, "newpad"); windowInit(); uiShow(); @@ -240,7 +239,7 @@ static void notify(uint id, const char *str) { utilPush(&util, buf); pid_t pid = fork(); - if (pid < 0) err(EX_OSERR, "fork"); + if (pid < 0) err(1, "fork"); if (pid) return; setsid(); @@ -249,7 +248,7 @@ static void notify(uint id, const char *str) { dup2(utilPipe[1], STDERR_FILENO); execvp(util.argv[0], (char *const *)util.argv); warn("%s", util.argv[0]); - _exit(EX_CONFIG); + _exit(127); } void uiWrite(uint id, enum Heat heat, const time_t *src, const char *str) { @@ -296,7 +295,7 @@ static size_t signatureVersion(uint64_t signature) { for (size_t i = 0; i < ARRAY_LEN(Signatures); ++i) { if (signature == Signatures[i]) return i; } - errx(EX_DATAERR, "unknown file signature %" PRIX64, signature); + errx(1, "unknown save file signature %" PRIX64, signature); } static int writeUint64(FILE *file, uint64_t u) { @@ -317,32 +316,32 @@ int uiSave(void) { static uint64_t readUint64(FILE *file) { uint64_t u; fread(&u, sizeof(u), 1, file); - if (ferror(file)) err(EX_IOERR, "fread"); - if (feof(file)) errx(EX_DATAERR, "unexpected eof"); + if (ferror(file)) err(1, "fread"); + if (feof(file)) errx(1, "unexpected end of save file"); return u; } void uiLoad(const char *name) { int error; saveFile = dataOpen(name, "a+e"); - if (!saveFile) exit(EX_CANTCREAT); + if (!saveFile) exit(1); rewind(saveFile); #ifdef __FreeBSD__ cap_rights_t rights; cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_FLOCK, CAP_FTRUNCATE); error = caph_rights_limit(fileno(saveFile), &rights); - if (error) err(EX_OSERR, "cap_rights_limit"); + if (error) err(1, "cap_rights_limit"); #endif error = flock(fileno(saveFile), LOCK_EX | LOCK_NB); if (error && errno == EWOULDBLOCK) { - errx(EX_CANTCREAT, "%s: save file in use", name); + errx(1, "%s: save file in use", name); } time_t signature; fread(&signature, sizeof(signature), 1, saveFile); - if (ferror(saveFile)) err(EX_IOERR, "fread"); + if (ferror(saveFile)) err(1, "fread"); if (feof(saveFile)) { return; } diff --git a/url.c b/url.c index 7da0968..349dc00 100644 --- a/url.c +++ b/url.c @@ -32,7 +32,6 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <sysexits.h> #include <unistd.h> #include "chat.h" @@ -67,7 +66,7 @@ static void compile(void) { if (!error) return; char buf[256]; regerror(error, &Regex, buf, sizeof(buf)); - errx(EX_SOFTWARE, "regcomp: %s: %s", buf, Pattern); + errx(1, "regcomp: %s: %s", buf, Pattern); } struct URL { @@ -92,10 +91,10 @@ static void push(uint id, const char *nick, const char *str, size_t len) { url->nick = NULL; if (nick) { url->nick = strdup(nick); - if (!url->nick) err(EX_OSERR, "strdup"); + if (!url->nick) err(1, "strdup"); } url->url = malloc(len + 1); - if (!url->url) err(EX_OSERR, "malloc"); + if (!url->url) err(1, "malloc"); char buf[1024]; snprintf(buf, sizeof(buf), "%.*s", (int)len, str); @@ -120,7 +119,7 @@ static const struct Util OpenUtils[] = { static void urlOpen(const char *url) { pid_t pid = fork(); - if (pid < 0) err(EX_OSERR, "fork"); + if (pid < 0) err(1, "fork"); if (pid) return; setsid(); @@ -132,7 +131,7 @@ static void urlOpen(const char *url) { utilPush(&util, url); execvp(util.argv[0], (char *const *)util.argv); warn("%s", util.argv[0]); - _exit(EX_CONFIG); + _exit(127); } for (size_t i = 0; i < ARRAY_LEN(OpenUtils); ++i) { struct Util util = OpenUtils[i]; @@ -140,11 +139,11 @@ static void urlOpen(const char *url) { execvp(util.argv[0], (char *const *)util.argv); if (errno != ENOENT) { warn("%s", util.argv[0]); - _exit(EX_CONFIG); + _exit(127); } } warnx("no open utility found"); - _exit(EX_CONFIG); + _exit(127); } struct Util urlCopyUtil; @@ -158,18 +157,18 @@ static const struct Util CopyUtils[] = { static void urlCopy(const char *url) { int rw[2]; int error = pipe(rw); - if (error) err(EX_OSERR, "pipe"); + if (error) err(1, "pipe"); size_t len = strlen(url); if (len > PIPE_BUF) len = PIPE_BUF; ssize_t n = write(rw[1], url, len); - if (n < 0) err(EX_IOERR, "write"); + if (n < 0) err(1, "write"); error = close(rw[1]); - if (error) err(EX_IOERR, "close"); + if (error) err(1, "close"); pid_t pid = fork(); - if (pid < 0) err(EX_OSERR, "fork"); + if (pid < 0) err(1, "fork"); if (pid) { close(rw[0]); return; @@ -183,17 +182,17 @@ static void urlCopy(const char *url) { if (urlCopyUtil.argc) { execvp(urlCopyUtil.argv[0], (char *const *)urlCopyUtil.argv); warn("%s", urlCopyUtil.argv[0]); - _exit(EX_CONFIG); + _exit(127); } for (size_t i = 0; i < ARRAY_LEN(CopyUtils); ++i) { execvp(CopyUtils[i].argv[0], (char *const *)CopyUtils[i].argv); if (errno != ENOENT) { warn("%s", CopyUtils[i].argv[0]); - _exit(EX_CONFIG); + _exit(127); } } warnx("no copy utility found"); - _exit(EX_CONFIG); + _exit(127); } void urlOpenCount(uint id, uint count) { @@ -239,7 +238,7 @@ static int writeString(FILE *file, const char *str) { } static ssize_t 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"); + if (len < 0 && !feof(file)) err(1, "getdelim"); return len; } @@ -269,11 +268,11 @@ void urlLoad(FILE *file, size_t version) { readString(file, &buf, &cap); if (buf[0]) { url->nick = strdup(buf); - if (!url->nick) err(EX_OSERR, "strdup"); + if (!url->nick) err(1, "strdup"); } readString(file, &buf, &cap); url->url = strdup(buf); - if (!url->url) err(EX_OSERR, "strdup"); + if (!url->url) err(1, "strdup"); } free(buf); } diff --git a/window.c b/window.c index 0c675e9..2e79a65 100644 --- a/window.c +++ b/window.c @@ -35,7 +35,6 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <sysexits.h> #include <time.h> #include "chat.h" @@ -93,7 +92,7 @@ static struct Window *windowRemove(uint num) { } static void windowFree(struct Window *window) { - cacheRemove(None, idNames[window->id]); + completeRemove(None, idNames[window->id]); bufferFree(window->buffer); free(window); } @@ -107,7 +106,7 @@ uint windowFor(uint id) { } struct Window *window = calloc(1, sizeof(*window)); - if (!window) err(EX_OSERR, "malloc"); + if (!window) err(1, "malloc"); window->id = id; window->mark = true; @@ -118,7 +117,7 @@ uint windowFor(uint id) { window->thresh = windowThreshold; } window->buffer = bufferAlloc(); - cacheInsert(false, None, idNames[id])->color = idColors[id]; + completePush(None, idNames[id], idColors[id]); return windowPush(window); } @@ -132,7 +131,7 @@ void windowInit(void) { struct tm *time = localtime(&(time_t) { -22100400 }); size_t len = strftime(buf, sizeof(buf), fmt, time); - if (!len) errx(EX_CONFIG, "invalid timestamp format: %s", fmt); + if (!len) errx(1, "invalid timestamp format: %s", fmt); int y; waddstr(uiMain, buf); @@ -147,6 +146,7 @@ static int styleAdd(WINDOW *win, struct Style init, const char *str) { struct Style style = init; while (*str) { size_t len = styleParse(&style, &str); + if (!len) continue; wattr_set(win, uiAttr(style), uiPair(style), NULL); if (waddnstr(win, str, len) == ERR) return -1; @@ -477,7 +477,7 @@ void windowClose(uint num) { if (num >= count) return; if (windows[num]->id == Network) return; struct Window *window = windowRemove(num); - cacheClear(window->id); + completeRemove(window->id, NULL); windowFree(window); if (swap >= num) swap--; if (show == num) { @@ -621,14 +621,14 @@ int windowSave(FILE *file) { static time_t readTime(FILE *file) { time_t time; fread(&time, sizeof(time), 1, file); - if (ferror(file)) err(EX_IOERR, "fread"); - if (feof(file)) errx(EX_DATAERR, "unexpected eof"); + if (ferror(file)) err(1, "fread"); + if (feof(file)) errx(1, "unexpected end of save file"); return time; } static ssize_t 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"); + if (len < 0 && !feof(file)) err(1, "getdelim"); return len; } diff --git a/xdg.c b/xdg.c index 75ee871..6f61cf9 100644 --- a/xdg.c +++ b/xdg.c @@ -32,7 +32,6 @@ #include <stdlib.h> #include <string.h> #include <sys/stat.h> -#include <sysexits.h> #include "chat.h" @@ -90,7 +89,7 @@ static char *basePath( } else if (home) { snprintf(buf, cap, "%s/%s/" SUBDIR "/%s", home, base.defHome, path); } else { - errx(EX_USAGE, "HOME unset"); + errx(1, "HOME unset"); } return buf; } |