ref: 86fd8d82baf0cc4ddb4f833b0f0844cfd684ba93
author: glenda <glenda@pi>
date: Mon Feb 16 07:59:12 EST 2026
libirc & chat
--- /dev/null
+++ b/README
@@ -1,0 +1,1 @@
+i want to thank JUSTIN CASE
--- /dev/null
+++ b/chat/chat.man
@@ -1,0 +1,68 @@
+.TH CHAT 1
+.SH NAME
+chat \- internet relay chat client
+.SH SYNOPSIS
+.B chat
+[
+.B -t
+] [
+.B -a
+.I address
+]
+.SH DESCRIPTION
+.I Chat
+is an
+.B IRC
+client. By default, it connects to oftc.net
+at port 6697 or the server specified in the
+.B $irc
+environment variable (see
+.IR dial (2))
+and reads raw
+.B IRC
+commands from standard input. Chat recognizes the following flags:
+.TP
+.B -t
+Connect using TLS.
+.TP
+.B -a
+Set the address to
+.I address.
+.PP
+.I Chat
+also recognizes the following control
+messages:
+.TP
+.B /q \fItarget\fR
+This opens a new window that relays
+messages to the
+.I target.
+The
+.I target
+can be a channel or a nick.
+The created window
+also accepts control messages.
+.TP
+.BI /x
+Closes a window and parts. If sent to
+the main window, the connection is closed
+and the program exits.
+.TP
+.B /n \fIdoofus\fR
+Change nicknames to
+.I doofus.
+.SH EXAMPLES
+Join #cat-v and then open a private
+message window with glenda:
+.br
+.ne 3
+.IP
+.EX
+chat
+/q #cat-v
+/q glenda
+.EE
+.SH SOURCE
+https://github.com/mischief/plan9
+.SH SEE ALSO
+.IR ircrc (1)
--- /dev/null
+++ b/chat/dat.h
@@ -1,0 +1,15 @@
+typedef struct Wind Wind;
+struct Wind
+{+ QLock;
+ u64int id;
+ char *target;
+
+ Channel *event; /* char* */
+
+ int kfd; /* rio cons */
+ int kpid; /* keyboard child */
+
+ Wind *prev;
+ Wind *next;
+};
--- /dev/null
+++ b/chat/fns.h
@@ -1,0 +1,13 @@
+/* util.c */
+u64int jenkinshash(char*, int);
+void riolabel(char*);
+void rioclose(void);
+int riowindow(char*);
+
+/* wind.c */
+Wind *windmk(char*);
+void windfree(Wind*);
+void windlink(Wind*, Wind*);
+void windunlink(Wind*, Wind*);
+Wind* windfind(Wind*, char*);
+void windappend(Wind*, char*, ...);
--- /dev/null
+++ b/chat/main.c
@@ -1,0 +1,315 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <thread.h>
+#include <fcall.h>
+#include <9p.h>
+#include <mp.h>
+#include <libsec.h>
+
+#include "irc.h"
+
+#include "dat.h"
+#include "fns.h"
+
+static Wind *mainwind; /* server window */
+static Irc *conn; /* our connection */
+static char *nick;
+
+static void windevents(Wind*);
+
+static void
+windproc(void *v)
+{+ char *name;
+ Wind *w;
+
+ name = v;
+
+ threadsetname("wind %s", name);+
+ /* copy ns since we fiddle with the namespace for graphics */
+ rfork(RFNAMEG);
+
+ if(riowindow("-scroll -dy 200") < 0)+ sysfatal("newwindow: %r");+
+ riolabel(name);
+
+ w = windmk(name);
+ windlink(mainwind, w);
+
+ free(name);
+
+ windevents(w);
+
+ windunlink(mainwind, w);
+ if(chanclosing(w->event) == -1)
+ chanclose(w->event);
+ windfree(w);
+
+ rioclose();
+ threadexits(nil);
+}
+
+enum
+{+ CMexit,
+ CMquery,
+ CMnick,
+};
+
+Cmdtab inputtab[] =
+{+ CMexit, "x", 1,
+ CMexit, "exit", 1,
+ CMquery, "q", 2,
+ CMquery, "query", 2,
+ CMnick, "n", 2,
+ CMnick, "nick", 2,
+};
+
+static void
+windevents(Wind *w)
+{+ char *s, *name;
+ Cmdbuf *cb;
+ Cmdtab *ct;
+
+ while((s = recvp(w->event)) != nil){+ if(s[0] == '/' && strlen(s) > 1){+ cb = parsecmd(s+1, strlen(s+1));
+ ct = lookupcmd(cb, inputtab, nelem(inputtab));
+ if(ct == nil){+ windappend(w, "* invalid command %q *", cb->f[0]);
+ continue;
+ }
+
+ switch(ct->index){+ case CMexit:
+ free(cb);
+ part:
+ if(w->target != nil && w->target[0] == '#')
+ ircpart(conn, w->target);
+ goto done;
+ break;
+
+ case CMquery:
+ if(cb->f[1][0] == '#')
+ ircjoin(conn, cb->f[1]);
+
+ name = strdup(cb->f[1]);
+ proccreate(windproc, name, 8192);
+ break;
+
+ case CMnick:
+ ircnick(conn, cb->f[1]);
+ break;
+ }
+ free(cb);
+ } else {+ if(w->target != nil){+ ircprivmsg(conn, w->target, s);
+ } else {+ ircraw(conn, s);
+ }
+ }
+ free(s);
+ }
+
+ /* make sure to part on rio del */
+ if(w->target != nil && w->target[0] == '#')
+ goto part;
+
+done:
+ return;
+}
+
+static void
+on433(Irc *irc, IrcMsg*, void*)
+{+ char buf[128];
+
+ snprint(buf, sizeof buf, "%s%d", nick, 10+nrand(1000));
+ windappend(mainwind, "* nickname %q in use, switching to %q *", nick, buf);
+
+ ircnick(irc, buf);
+}
+
+// NOTICE/PRIVMSG
+static void
+msg(Irc*, IrcMsg *m, void*)
+{+ int r;
+ char buf[512], prefix[256];
+ char *s;
+ Wind *w;
+
+ /* user privmsg */
+ strcpy(prefix, m->prefix);
+ if((s = strchr(prefix, '!')) != nil)
+ *s = 0;
+
+ snprint(buf, sizeof buf, "%s→ ", prefix);
+ for(r = 1; r < m->nargs; r++){+ strcat(buf, " ");
+ strcat(buf, m->args[r]);
+ }
+
+ if(m->args[0][0] == '#'){+ w = windfind(mainwind, m->args[0]);
+ } else {+ w = windfind(mainwind, prefix);
+ }
+
+ if(w != nil){+ windappend(w, buf);
+ } else {+ windappend(mainwind, buf);
+ }
+}
+
+static int
+inset(char *needle, char **haystack){+ char **p;
+ for(p = haystack; *p != nil; (*p)++)
+ if(cistrcmp(needle, *p) == 0)
+ return 0;
+
+ return -1;
+}
+
+static void
+catchall(Irc*, IrcMsg *m, void*)
+{+ int i;
+ char buf[512];
+
+ if(m->cmd[0] == '_')
+ return;
+
+ char *unwanted[]={+ IRC_PING,
+ IRC_NOTICE,
+ IRC_PRIVMSG,
+ nil,
+ };
+
+ if(inset(m->cmd, unwanted) == 0)
+ return;
+
+ snprint(buf, sizeof buf, "%s→ ", m->prefix);
+ for(i = 1; i < m->nargs; i++){+ strcat(buf, " ");
+ strcat(buf, m->args[i]);
+ }
+
+ windappend(mainwind, buf);
+}
+
+static void
+ondisconnect(Irc *, IrcMsg*, void*)
+{+ windappend(mainwind, "* disconnected *");
+}
+
+static void
+onconnect(Irc *irc, IrcMsg*, void*)
+{+ Wind *w;
+
+ windappend(mainwind, "* connected *");
+ ircuser(irc, "none", "0", "none");
+ ircnick(irc, nick);;
+
+ qlock(mainwind);
+ for(w = mainwind; w != nil; w = w->next){+ if(w->target != nil && w->target[0] == '#')
+ ircjoin(irc, w->target);
+ }
+ qunlock(mainwind);
+}
+
+static void
+ircproc(void *v)
+{+ Irc *irc;
+
+ irc = v;
+
+ procsetname("irc %s", ircconf(irc)->address);+
+ ircrun(irc);
+}
+
+static void
+usage(void)
+{+ fprint(2, "usage: %s [-t] [-a address]\n", argv0);
+ threadexitsall("usage");+}
+
+static int
+tlsdial(Irc *, IrcConf *conf)
+{+ TLSconn c;
+ int fd;
+
+ fd = dial(conf->address, nil, nil, nil);
+ if(fd < 0)
+ return -1;
+ return tlsClient(fd, &c);
+}
+
+void
+threadmain(int argc, char *argv[])
+{+ IrcConf conf = { 0 };+ char *address = getenv("irc");+
+ ARGBEGIN{+ case 't':
+ conf.dial = tlsdial;
+ break;
+ case 'a':
+ address = EARGF(usage());
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 0)
+ usage();
+
+ quotefmtinstall();
+ srand(truerand());
+
+ rfork(RFNOTEG);
+
+ if(address == nil)
+ address = "tcp!irc.oftc.net!6697";
+
+ conf.address = address;
+ nick = getuser();
+
+ riolabel(address);
+
+ mainwind = windmk(nil);
+
+ conn = ircinit(&conf);
+ irchook(conn, IRC_CONNECT, onconnect);
+ irchook(conn, IRC_DISCONNECT, ondisconnect);
+ irchook(conn, IRC_ERR_NICKNAMEINUSE, on433);
+ irchook(conn, IRC_PRIVMSG, msg);
+ irchook(conn, IRC_NOTICE, msg);
+ irchook(conn, nil, catchall);
+
+ proccreate(ircproc, conn, 16*1024);
+
+ procsetname("main window");+
+ windevents(mainwind);
+
+ postnote(PNGROUP, getpid(), "die yankee pig dog");
+ threadexitsall(nil);
+}
--- /dev/null
+++ b/chat/mkfile
@@ -1,0 +1,19 @@
+</$objtype/mkfile
+
+BIN=/$objtype/bin
+TARG=chat
+OFILES=\
+ main.$O\
+ util.$O\
+ wind.$O\
+
+LIB=/$objtype/lib/libirc.a
+
+MAN=/sys/man/1
+
+</sys/src/cmd/mkone
+
+install:V: man
+
+uninstall:V:
+ rm -f $BIN/$TARG $MAN/$TARG
--- /dev/null
+++ b/chat/util.c
@@ -1,0 +1,67 @@
+#include <u.h>
+#include <libc.h>
+
+u64int jenkinshash(char *key, int len)
+{+ u64int hash, i;
+ for(hash = i = 0; i < len; ++i){+ hash += key[i];
+ hash += (hash << 10);
+ hash ^= (hash >> 6);
+ }
+ hash += (hash << 3);
+ hash ^= (hash >> 11);
+ hash += (hash << 15);
+ return hash;
+}
+
+static void
+fwrites(char *f, char *s)
+{+ int fd;
+ fd = open(f, OWRITE);
+ if(fd > 0){+ write(fd, s, strlen(s));
+ close(fd);
+ }
+}
+
+void
+riolabel(char *label)
+{+ fwrites("/dev/label", label);+}
+
+void
+rioclose(void)
+{+ fwrites("/dev/wctl", "delete");+}
+
+/* copied from /sys/src/libdraw/newwindow.c to avoid linking in libdraw. */
+int
+riowindow(char *str)
+{+ int fd;
+ char *wsys;
+ char buf[256];
+
+ wsys = getenv("wsys");+ if(wsys == nil)
+ return -1;
+ fd = open(wsys, ORDWR);
+ if(fd < 0){+ free(wsys);
+ return -1;
+ }
+ rfork(RFNAMEG);
+ unmount(wsys, "/dev"); /* drop reference to old window */
+ free(wsys);
+ if(str)
+ snprint(buf, sizeof buf, "new %s", str);
+ else
+ strcpy(buf, "new");
+ if(mount(fd, -1, "/mnt/wsys", MREPL, buf) < 0)
+ return mount(fd, -1, "/dev", MBEFORE, buf);
+ return bind("/mnt/wsys", "/dev", MBEFORE);+}
--- /dev/null
+++ b/chat/wind.c
@@ -1,0 +1,131 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+
+#include "dat.h"
+#include "fns.h"
+
+static void
+kbdproc(void *v)
+{+ char line[512];
+ int r;
+ Wind *w;
+
+ w = v;
+
+ threadsetname("kbdproc");+
+ while((r = read(w->kfd, line, sizeof line - 1)) > 0){+ if(r > 0 && line[r] == '\n')
+ r--;
+
+ line[r] = 0;
+
+ chanprint(w->event, "%s", line);
+ }
+
+ chanclose(w->event);
+}
+
+Wind*
+windmk(char *target)
+{+ int r;
+ Wind *w;
+
+ w = mallocz(sizeof(*w), 1);
+ if(target != nil){+ for(r = 0; r < strlen(target); r++)
+ target[r] = tolower(target[r]);
+
+ w->id = jenkinshash(target, strlen(target));
+ }
+
+ if(target != nil)
+ w->target = strdup(target);
+
+ w->event = chancreate(sizeof(char*), 0);
+ w->kpid = -1;
+
+ if((w->kfd = open("/dev/cons", ORDWR)) < 0){+ sysfatal("open: %r");+ }
+
+ w->kpid = proccreate(kbdproc, w, 8192);
+ return w;
+}
+
+void
+windfree(Wind *w)
+{+ qlock(w);
+
+ if(w->kpid > 0)
+ postnote(PNPROC, w->kpid, "die yankee pig dog");
+
+ if(w->event != nil)
+ chanfree(w->event);
+
+ free(w->target);
+ free(w);
+}
+
+void
+windlink(Wind *l, Wind *w)
+{+ qlock(l);
+ w->prev = l;
+ w->next = l->next;
+ l->next = w;
+ qunlock(l);
+}
+
+void
+windunlink(Wind *l, Wind *w)
+{+ qlock(l);
+ if(w->next != nil)
+ w->next->prev = w->prev;
+ if(w->prev != nil)
+ w->prev->next = w->next;
+ qunlock(l);
+}
+
+Wind*
+windfind(Wind *l, char *target)
+{+ int r;
+ u64int id;
+ Wind *w;
+
+ for(r = 0; r < strlen(target); r++)
+ target[r] = tolower(target[r]);
+
+ id = jenkinshash(target, strlen(target));
+
+ qlock(l);
+ for(w = l; w != nil; w = w->next){+ if(w->id == id)
+ break;
+ }
+ qunlock(l);
+
+ return w;
+}
+
+void
+windappend(Wind *w, char *msg, ...)
+{+ va_list arg;
+
+ qlock(w);
+
+ va_start(arg, msg);
+ vfprint(w->kfd, msg, arg);
+ va_end(arg);
+
+ write(w->kfd, "\n", 1);
+
+ qunlock(w);
+}
--- /dev/null
+++ b/libirc/irc.c
@@ -1,0 +1,342 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+
+#include "irc.h"
+
+typedef struct Hook Hook;
+struct Hook
+{+ char what[64];
+ IrcCb *cb;
+ Hook *next;
+};
+
+struct Irc
+{+ QLock;
+
+ IrcConf *conf;
+
+ int fd;
+ Biobuf *buf;
+ Hook *head;
+
+ int run;
+};
+
+IrcMsg*
+parsemsg(char *b)
+{+ char *tok[5], *args[20];
+ int i, n, ntok, nargs;
+ IrcMsg *msg;
+
+ if(b == nil || b[0] == 0)
+ return nil;
+
+ msg = mallocz(sizeof(*msg), 1);
+ if(msg == nil)
+ return nil;
+
+ if(b[0] == ':'){+ ntok = gettokens(b, tok, 2, " ");
+ if(ntok != 2)
+ goto fail;
+ strncpy(msg->prefix, tok[0]+1, sizeof(msg->prefix)-1);
+ b = tok[1];
+ }
+
+ ntok = gettokens(b, tok, 2, ":");
+
+ nargs = gettokens(tok[0], args, nelem(args), " ");
+
+ strncpy(msg->cmd, args[0], sizeof(msg->cmd)-1);
+ n = strlen(msg->cmd);
+ for(i = 0; i < n; i++){+ msg->cmd[i] = tolower(msg->cmd[i]);
+ }
+
+ msg->nargs = nargs - 1 + (ntok > 1 ? 1 : 0);
+
+ msg->args = mallocz(msg->nargs * sizeof(char*), 1);
+ if(msg->args == nil)
+ goto fail;
+
+ for(i = 0; i < msg->nargs - (ntok > 1 ? 1 : 0); i++){+ msg->args[i] = strdup(args[i+1]);
+ }
+
+ if(ntok > 1){+ msg->args[i] = strdup(tok[1]);
+ }
+
+/*
+ fprint(2, "prefix: %s\n", msg->prefix);
+ fprint(2, "command: %s\n", msg->cmd);
+ for(i = 0; i < msg->nargs; i++){+ fprint(2, " %s\n", msg->args[i]);
+ }
+*/
+
+ return msg;
+fail:
+ free(msg);
+ return nil;
+}
+
+void
+freemsg(IrcMsg *m)
+{+ int i;
+
+ assert(m != nil);
+
+ if(m->nargs > 0){+ for(i = 0; i < m->nargs; i++)
+ free(m->args[i]);
+ free(m->args);
+ }
+
+ free(m);
+}
+
+void
+pinghandler(Irc *irc, IrcMsg *msg, void*)
+{+ if(msg->nargs > 0)
+ ircrawf(irc, "PONG :%s", msg->args[0]);
+}
+
+Irc*
+ircinit(IrcConf *conf)
+{+ Irc *c;
+
+ c = mallocz(sizeof(*c), 1);
+ if(c == nil)
+ return nil;
+
+ c->conf = conf;
+ c->fd = -1;
+ c->run = 1;
+
+ if(irchook(c, IRC_PING, pinghandler) < 0)
+ goto bad1;
+
+ return c;
+
+bad1:
+ free(c);
+ return nil;
+}
+
+void
+ircfree(Irc *irc)
+{+ Hook *h, *next;
+
+ for(h = irc->head, next = h->next; next != nil; h = next, next = next->next){+ free(h);
+ }
+
+ if(irc->buf)
+ Bterm(irc->buf);
+}
+
+static void
+callhook(Irc *irc, char *what, IrcMsg *msg){+ Hook *h;
+ IrcMsg fake;
+
+ if(msg == nil){+ msg = &fake;
+ memset(msg, 0, sizeof(*msg));
+ strcpy(msg->cmd, what);
+ }
+
+ for(h = irc->head; h != nil; h = h->next)
+ if(h->what[0] == 0 || cistrcmp(what, h->what) == 0)
+ h->cb(irc, msg, irc->conf->aux);
+}
+
+int
+ircrun(Irc *irc)
+{+ int l;
+ char *p;
+ IrcMsg *msg;
+ Biobuf *buf;
+
+ buf = nil;
+
+ for(;;){+ qlock(irc);
+ if(!irc->run)
+ break;
+ qunlock(irc);
+
+ switch(irc->fd){+ case -1:
+ callhook(irc, IRC_DIALING, nil);
+
+ if(irc->conf->dial != nil)
+ irc->fd = irc->conf->dial(irc, irc->conf);
+ else
+ irc->fd = dial(irc->conf->address, nil, nil, nil);
+
+ if(irc->fd >= 0){+ buf = Bfdopen(irc->fd, OREAD);
+ if(buf == nil){+ callhook(irc, IRC_OOM, nil);
+ break;
+ }
+
+ callhook(irc, IRC_CONNECT, nil);
+ }
+
+ sleep(5000);
+
+ break;
+ default:
+ p = Brdstr(buf, '\n', 1);
+ if(p == nil){+ // eof/maybe oom?
+ Bterm(buf);
+ buf = nil;
+ irc->fd = -1;
+ callhook(irc, IRC_DISCONNECT, nil);
+ break;
+ }
+
+ l = Blinelen(buf);
+ if(l > 0 && p[l-1] == '\r')
+ p[l-1] = 0;
+
+ if(irc->conf->debug)
+ fprint(2, "<< %s\n", p);
+
+ msg = parsemsg(p);
+ free(p);
+ if(msg == nil){+ callhook(irc, IRC_OOM, nil);
+ break;
+ }
+
+ if(strcmp(msg->cmd, IRC_PING) == 0){+ ircrawf(irc, "PONG: %s", msg->args[0]);
+ }
+
+ callhook(irc, msg->cmd, msg);
+
+ freemsg(msg);
+
+ break;
+ }
+ }
+
+ if(buf != nil)
+ Bterm(buf);
+
+ return 0;
+}
+
+int
+irchook(Irc *irc, char *what, IrcCb *cb)
+{+ Hook *h;
+
+ h = mallocz(sizeof(*h), 1);
+ if(h == nil)
+ return -1;
+
+ if(what == nil)
+ what = "";
+
+ snprint(h->what, sizeof(h->what), "%s", what);
+ h->cb = cb;
+ h->next = irc->head;
+
+ irc->head = h;
+
+ return 0;
+}
+
+IrcConf*
+ircconf(Irc *irc)
+{+ return irc->conf;
+}
+
+void
+ircterminate(Irc *irc)
+{+ qlock(irc);
+ irc->run = 0;
+ close(irc->fd);
+ qunlock(irc);
+}
+
+int
+ircraw(Irc *irc, char *msg)
+{+ int rv;
+
+ if(irc->conf->debug)
+ fprint(2, ">> %s\n", msg);
+
+ rv = write(irc->fd, msg, strlen(msg));
+ if(rv > 0)
+ rv = write(irc->fd, "\r\n", 2);
+ return rv;
+}
+
+int
+ircrawf(Irc *irc, char *fmt, ...)
+{+ char tmp[512];
+ va_list arg;
+
+ va_start(arg, fmt);
+
+ tmp[0] = 0;
+ vsnprint(tmp, sizeof(tmp), fmt, arg);
+
+ return ircraw(irc, tmp);
+}
+
+int
+ircquit(Irc *irc, char *why)
+{+ return ircrawf(irc, "QUIT :%s", why);
+}
+
+int
+ircuser(Irc *irc, char *user, char *mode, char *realname)
+{+ return ircrawf(irc, "USER %s %s * :%s", user, mode, realname);
+}
+
+int
+ircnick(Irc *irc, char *nickname)
+{+ return ircrawf(irc, "NICK %s", nickname);
+}
+
+int
+ircjoin(Irc *irc, char *channel)
+{+ return ircrawf(irc, "JOIN %s", channel);
+}
+
+int
+ircpart(Irc *irc, char *channel)
+{+ return ircrawf(irc, "PART %s", channel);
+}
+
+int
+ircprivmsg(Irc *irc, char *tgt, char *msg)
+{+ return ircrawf(irc, "PRIVMSG %s :%s", tgt, msg);
+}
--- /dev/null
+++ b/libirc/irc.h
@@ -1,0 +1,50 @@
+typedef struct IrcConf IrcConf;
+typedef struct Irc Irc;
+#pragma incomplete Irc
+typedef struct IrcMsg IrcMsg;
+typedef struct IrcHooks IrcHooks;
+
+typedef void IrcCb(Irc*, IrcMsg*, void *aux);
+
+struct IrcConf
+{+ char *address;
+ int debug;
+ void *aux;
+ int (*dial)(Irc *irc, IrcConf *conf);
+};
+
+struct IrcMsg
+{+ char prefix[256];
+ char cmd[64];
+ char **args;
+ int nargs;
+};
+
+#define IRC_DISCONNECT "_DISCONNECTED"
+#define IRC_CONNECT "_CONNECTED"
+#define IRC_DIALING "_DIALING"
+#define IRC_OOM "_OOM"
+
+#define IRC_PING "PING"
+#define IRC_NOTICE "NOTICE"
+#define IRC_PRIVMSG "PRIVMSG"
+
+#define IRC_RPL_WELCOME "001"
+#define IRC_ERR_NICKNAMEINUSE "433"
+
+Irc* ircinit(IrcConf *conf);
+void ircfree(Irc*);
+int ircrun(Irc*);
+int irchook(Irc *irc, char *what, IrcCb *cb);
+void ircterminate(Irc *irc);
+IrcConf *ircconf(Irc*);
+int ircraw(Irc *irc, char *msg);
+int ircrawf(Irc *irc, char *fmt, ...);
+int ircquit(Irc *irc, char *why);
+int ircuser(Irc *irc, char *user, char *mode, char *realname);
+int ircnick(Irc *irc, char *nickname);
+int ircjoin(Irc *irc, char *channel);
+int ircpart(Irc *irc, char *channel);
+int ircprivmsg(Irc *irc, char *tgt, char *msg);
binary files /dev/null b/libirc/libirc.7.a differ
--- /dev/null
+++ b/libirc/mkfile
@@ -1,0 +1,20 @@
+</$objtype/mkfile
+
+P=irc
+
+LIB=lib$P.$O.a
+OFILES=$P.$O
+HFILES=/sys/include/$P.h
+
+</sys/src/cmd/mklib
+
+install:V: $LIB
+ cp $LIB /$objtype/lib/lib$P.a
+ cp $P.h /sys/include/$P.h
+ #cp $P.man2 /sys/man/2/$P
+
+uninstall:V:
+ rm -f /$objtype/lib/lib$P.a /sys/include/$P.h /sys/man/2/$P
+
+$O.test: test.$O $LIB
+ $LD -o $target $prereq
--
⑨