--- /dev/null
+#!/bin/rc
+if(~ $#* 0){
+ echo usage: Acid pid >[2=1]
+ exit usage
+}
+win acid -l acme $*
--- /dev/null
+broke|rc kill program|rc
+Acid pid
+Acid -l thread -l acidfile pid
--- /dev/null
+#!/bin/rc
+
+if(! test -f /mnt/apm/battery){
+ echo no apm >[1=2]
+ exit 'no apm'
+}
+
+cd /mnt/acme/new
+echo name /dev/apm >ctl
+echo dump Battery >ctl
+
+awkscript='
+NR==1 {
+ if($3 != -1)
+ printf("%d%% %d:%02d %s", $2, $3/3600, ($3/60)%60, $1);
+ else
+ printf("%d%% %s", $2, $1);
+}
+'
+
+fn chk {
+ what=`{awk $awkscript /mnt/apm/battery}
+ echo cleartag >ctl || exit die
+ echo clean >ctl || exit die
+ echo ' '^$"what >tag || exit die
+}
+
+chk
+while(sleep 60)
+ chk
--- /dev/null
+#!/bin/rc
+
+if(! ~ $#* 0){
+ echo usage: Isspam >[1=2]
+ exit usage
+}
+
+if(! ~ `{pwd} /mail/fs/*/[0-9]* || ! test -f raw || ! test -f unixheader){
+ echo must run in mail directory >[1=2]
+ exit 'bad dir'
+}
+
+cat unixheader raw | upas/isspam
--- /dev/null
+#!/bin/rc
+
+#/mail/fs is read-protected unless fs is mounted
+test -r /mail/fs || {
+ if(test -d /mnt/term/mail/fs/mbox) bind /mnt/term/mail/fs /mail/fs
+ if not upas/fs
+}
+
+exec /acme/bin/$objtype/Mail $*
--- /dev/null
+#!/bin/rc
+
+# aperl:
+# Executes perl command and alters stderr to produce Acme-friendly error messages
+# Created 02-JUL-1996, Luther Huffman, lutherh@stratcom.com
+
+/bin/perl $* |[2] /bin/perl -pe 's/ line (\d+)/:$1 /' >[1=2]
--- /dev/null
+The source directory should be called ./src instead of ./source,
+but this directory is bound into /bin and there is a command called
+src that the local directory would hide.
--- /dev/null
+#!/bin/rc
+
+if(! ~ $#* 0){
+ echo usage: Spam >[1=2]
+ exit usage
+}
+
+if(! ~ `{pwd} /mail/fs/*/[0-9]* || ! test -f raw || ! test -f unixheader){
+ echo must run in mail directory >[1=2]
+ exit 'bad dir'
+}
+
+cat unixheader raw | upas/spam
--- /dev/null
+#!/bin/rc
+
+if(! ~ $#* 0){
+ echo usage: Unspam >[1=2]
+ exit usage
+}
+
+if(! ~ `{pwd} /mail/fs/*/[0-9]* || ! test -f raw || ! test -f unixheader){
+ echo must run in mail directory >[1=2]
+ exit 'bad dir'
+}
+
+cat unixheader raw | upas/unspam
--- /dev/null
+#!/bin/rc
+
+if(~ $#* 0 1){
+ echo >[1=2] usage: adiff file1 file2
+ echo >[1=2] or adiff file1 file2... dir
+ exit usage
+}
+
+dir = /mnt/wsys
+if(! test -f $dir/cons)
+ dir = /mnt/term/$dir
+id=`{cat $dir/new/ctl}
+id=$id(1)
+
+l=$1
+r=$2
+if (test -d $1) l=$1/`{basename $2}
+if not if (test -d $2) r=$2/`{basename $1}
+
+echo 'name '^`{pwd}^/-diff-^`{basename $l} > $dir/$id/ctl
+
+diff $* | awk -v 'l='$l -v 'r='^$r '/^diff/ {l=$2; r=$3; next} /^[1-9]/ {sub("[acd]", " & " r ":"); sub("^", l ":", $0)}
+ {print $0}' > $dir/$id/body
+echo clean > $dir/$id/ctl
--- /dev/null
+#!/bin/rc
+
+exec grep -n $* /dev/null
--- /dev/null
+#!/bin/rc
+args=''
+while(~ $1 -*) {
+ args=$args^' '^$1
+ shift 1
+}
+if (~ $#1 0)
+ sysname=alice
+if not
+ sysname=$1
+if (! test -f /n/$sysname/usr/spool/ap ) { 9fs $sysname }
+eval exec /acme/bin/$cputype/apread $args $sysname
--- /dev/null
+#!/bin/rc
+
+spellflags=()
+fflag=''
+for(x){
+ switch($x){
+ case -[bcvx]
+ spellflags=($spellflags $x)
+ case -f
+ fflag=$x
+ case *
+ if(~ $fflag -f) {
+ spellflags=($spellflags -f $x)
+ fflag=''
+ }
+ if not args = ($args $x)
+ }
+}
+
+dir = /mnt/wsys
+if(! test -f $dir/cons)
+ dir = /mnt/term/$dir
+id=`{cat $dir/new/ctl}
+id=$id(1)
+
+if(~ $#args 1 && ~ $args /*){
+ adir = `{basename -d $args}
+ args = `{basename $args}
+ echo 'name '^$adir^/-spell > $dir/$id/ctl
+ cd $adir
+}
+if not {
+ echo 'name '^`{pwd}^/-spell > $dir/$id/ctl
+}
+
+{
+ echo noscroll
+ if(~ $#args 0)
+ /acme/bin/$cputype/spout | sort -t: -u +2 | sort -t: +1.1n | aux/sprog -a $spellflags > $dir/$id/body
+ if not for(i in $args)
+ /acme/bin/$cputype/spout $i | sort -t: -u +2 | sort -t: +1.1n | aux/sprog -a $spellflags > $dir/$id/body
+ echo clean
+}> $dir/$id/ctl
--- /dev/null
+win
+aspell file
+adiff file1 file2
+adict -d oed
--- /dev/null
+#!/bin/rc
+
+sed 's/^/ /' $*
--- /dev/null
+#!/bin/rc
+
+id=`{cat /mnt/acme/new/ctl}
+id=$id(1)
+cmd = $*
+if(~ $#cmd 0) cmd = cat
+
+echo 'name '^`{pwd}^/-^`{basename $cmd(1)} > /mnt/acme/$id/ctl
+$cmd > /mnt/acme/$id/body
+echo clean > /mnt/acme/$id/ctl
--- /dev/null
+#!/bin/rc
+
+sed 's/^/> /' $*
--- /dev/null
+This is a CD player for use under Acme.
+
+It is derived from my earlier cdplay, which
+was in turn derived from a 2nd edition player
+called vcd. I think hardly any of the code from
+vcd is left anymore, but it's what got me started.
+Vcd was originally by David Hogan with additions
+by Alberto Nava. David Hogan claims the only
+code left is the definition of struct Msf.
+
+Run it by executing "acd /dev/sdD0", where
+/dev/sdD0 is your CD reader.
+
+A window with a track list will appear, with
+tracks named Track 1, Track 2, etc.
+If it can be found in the freedb.org CD database,
+real track names will replace the boring
+ones before long.
+
+To start playing a track, right click the number.
+A "> " marks the currently playing track.
+When that track finishes, acd plays the track
+on the next line. This means you can edit
+the window as thought it were a play list.
+
+If the next line is "repeat", acd will start again
+at the first song listed in the window.
+
+CD changes are handled gracefully.
+
+Russ Cox
+9 August 2000
+rsc@plan9.bell-labs.com
--- /dev/null
+TWO FORMS OF ACCESS TO THE FREEDB\r
+---------------------------------\r
+\r
+In the following document we will refer to CDDB instead of freedb, since \r
+from a technical point of view, freedb is a CDDB-Server as it uses the \r
+CDDB-protocol.\r
+\r
+If you are interested in incorporating the use of freedb in your\r
+software, there are two forms of access that you may consider.\r
+\r
+1. <a href="#local">Local access</a>\r
+\r
+ In this mode your software simply attempts to open local files on\r
+ the computer to access the CDDB.\r
+\r
+2. <a href="#remote">Remote access</a>\r
+\r
+ In this mode the software must connect to a freedb server on the\r
+ network to access the CDDB. There is a CDDB server protocol that\r
+ the software (also known as the "client") must use to converse with\r
+ the server.\r
+\r
+You may choose to support either one, or both of these modes.\r
+\r
+\r
+CDDB DISCID\r
+-----------\r
+\r
+Both forms of CDDB access requires that the software computes a "disc\r
+ID" which is an identifier that is used to access the CDDB. The disc\r
+ID is a 8-digit hexadecimal (base-16) number, computed using data from\r
+a CD's Table-of-Contents (TOC) in MSF (Minute Second Frame) form. The\r
+algorithm is listed below in the <a href="http://freedb.freedb.org/sections.php?op=viewarticle&artid=6">DISCID Howto</a>.\r
+\r
+It is crucial that your software compute the disc ID correctly. If it\r
+does not generate the disc ID, it will not be compatible with the\r
+CDDB. Moreover, if your software submits CDDB entries with bad disc\r
+IDs to the freedb archives, it could compromise the integrity of the\r
+freedb.\r
+\r
+If you have access to a UNIX platform that xmcd supports, we suggest \r
+installing xmcd, and then test the disc ID code in your software by\r
+comparing the disc ID generated by xmcd with that of your software,\r
+for as large a number of CDs as possible.\r
+\r
+\r
+<a name="local"></a>LOCAL CDDB ACCESS\r
+-----------------\r
+\r
+There are two forms of the CDDB archive available, the standard form\r
+and the alternate form. Both forms are available for download from \r
+various servers. You can always find an actual list of mirrors on the \r
+freedb-homepage at <a href="http://freedb.freedb.org">http://freedb.freedb.org</a>.\r
+The standard form of the CDDB archive is released to the public as \r
+a UNIX tar(1)-format archive, compressed with gzip. The alternate \r
+form archive is in the .zip format that is popular on the Windows \r
+platform.\r
+\r
+Standard Form:\r
+--------------\r
+\r
+Each CD entry is a separate file in the xmcd CDDB. These files are\r
+organized in several directories, each directory is a category of\r
+music. Currently the "official" categories are listed as follows:\r
+\r
+ blues\r
+ classical\r
+ country\r
+ data\r
+ folk\r
+ jazz\r
+ misc\r
+ newage\r
+ reggae\r
+ rock\r
+ soundtrack\r
+\r
+The individual CDDB files have a file name that is the 8-digit disc\r
+ID. For example, under the blues directory there may be the following\r
+files:\r
+\r
+ 0511c012\r
+ 060e7314\r
+ 0c01e902\r
+ 0f0c3112\r
+ ...\r
+ fa0f6f10\r
+ fb0f8814\r
+ fd0e6013\r
+\r
+To access the CDDB entry associated with a CD, your software simply\r
+opens the appropriate file and reads the information.\r
+\r
+The content of each of these files is in a format described in the \r
+<a href="http://freedb.freedb.org/software/old/DBFORMAT">database-format specification</a>.\r
+\r
+Different pressings of a particular CD title may contain differences\r
+in timings that can cause the computed disc ID to be different.\r
+The CDDB allows this by having multiple file names be links to\r
+the same file. The links are implemented as actual filesystem links\r
+(see the ln(1) command) on UNIX systems. For example, the following\r
+files in the rock directory are all links to the same file, and\r
+refer to the CD "Pink Floyd / The Division Bell".:\r
+\r
+ 850f740b\r
+ 850f950b\r
+ 850f970b\r
+ 860f960b\r
+ 890f970b\r
+\r
+Xmcd and the CD database server use this form of the CDDB archive. The\r
+benefit of the standard form of the CDDB archive is very fast access,\r
+and ease of add/delete/edit operations on entries.\r
+\r
+Alternate Form:\r
+---------------\r
+\r
+Due to limitations in the FAT file system used on Windows 9x and \r
+Windows ME, it is unfeasible to use the standard format CDDB archive \r
+due to the large number of files. This is because such a filesystem \r
+operates on fixed-size clusters and even a small file (and most CDDB\r
+files are 1KB or less) would consume the space of a full cluster\r
+(Depending upon disk size, a cluster can range from 4KB to 32KB in \r
+size). Thus, a tremendous amount of disk space would be wasted on\r
+these systems if the CDDB archive is used in its standard form.\r
+\r
+An alternate form of the CDDB archives was created for use by software\r
+that must operate on a system with the FAT limitations.\r
+\r
+The alterate form still use the separate category directories as the\r
+standard form, but concatenates many files into a smaller number of\r
+files under each category. The first two digits of the CDDB file names\r
+is used as a key for concatenation, each file is allowed to grow to\r
+approximately 64KB in size before a new file is started. The file name\r
+indicates what range of the digits are included in that file. For\r
+example, under the blues category we may have the following files:\r
+\r
+ 01to36\r
+ 37to55\r
+ 56to71\r
+ ...\r
+ b2tod7\r
+ d8toff\r
+\r
+The 01to36 file contains all CDDB entries with disc ID 01xxxxxx,\r
+02xxxxxx, 03xxxxxx and so on, up to 36xxxxxx.\r
+\r
+Each entry in the concatenated file begins with the keyword\r
+\r
+#FILENAME=xxxxxxxx\r
+\r
+where discid is the 8-digit hexadecimal disc ID of that entry. Your\r
+software must search through the appropriate file to locate the desired\r
+entry. The CDDB entry is in the format described in Appendix B below.\r
+\r
+The alternate form avoids the problem of inefficient disk space\r
+utilization on FAT-based filesystems, but is slower to access than the\r
+standard form, and it is much more cumbersome to perform add/delete/edit\r
+operations on a CDDB entry.\r
+\r
+\r
+<a name="remote"></a>REMOTE CDDB ACCESS\r
+------------------\r
+\r
+Your software must be able to communicate with a remote CD server\r
+system via TCP/IP or HTTP. \r
+There are a number of public freedb servers operating\r
+on the Internet. The <a href="http://freedb.freedb.org/sections.php?op=viewarticle&artid=9">current list of public servers</a> is listed on the\r
+freedb web page at:\r
+\r
+ http://freedb.freedb.org.\r
+ \r
+It may also be obtained programmatically via the CDDB protocol "sites" \r
+command.\r
+\r
+TCP/IP access:\r
+\r
+All current freedb servers answer at TCP port 888. There may be future\r
+sites that deviate from this convention, however.\r
+\r
+HTTP access:\r
+\r
+The freedb-servers can be accessed via the cddb.cgi. This is resides at the\r
+following path: /~cddb/cddb.cgi \r
+Thus, the URL for accessing the server at freedb.freedb.org is:\r
+http://freedb.freedb.org/~cddb/cddb.cgi\r
+\r
+You should make the freedb server host (or hosts) and port numbers\r
+user-configurable in your software. Do not hard-wire the list of\r
+CD database servers into your code. The list of active servers changes\r
+over time.\r
+\r
+The CDDB server protocol is described in the <a href="http://freedb.freedb.org/sections.php?op=viewarticle&artid=28">CDDB-protocol documentation</a>.\r
+\r
+The CDDB entry returned from the server via a "cddb read" command is in\r
+the format described <a href="http://freedb.freedb.org/software/old/DBFORMAT">database-format specification</a>.\r
+\r
+You may experiment with the freedb server by connecting to port 888 of\r
+the server host via the "telnet" program, and then typing the cddb\r
+protocol commands by hand. For example:\r
+\r
+ telnet freedb.freedb.org 888\r
+\r
+connects you to the freedb server at freedb.freedb.org.\r
+\r
+Some additional notes for accessing freedb over the Internet:\r
+\r
+Your application should always specify the highest documented protocol\r
+level. The highest level currently supported is "3". Lower protocol \r
+levels will work, but are only provided for compatibility with older \r
+CDDB applications. If you do not use the highest available protocol \r
+level, certain important features will not be available to your \r
+application.\r
+\r
+Make sure to use the proper arguments with the "hello" command. The user\r
+and hostname arguments should be that of the user's email address, not\r
+some fixed hard-coded value. The application name and version should be\r
+that of your application, not that of another existing application.\r
+\r
+We consider the use of the "cddb query" command mandatory for all CDDB\r
+clients. It is not valid to issue a "cddb read" command without issuing\r
+a prior "cddb query" and receiving a good response, as it may yield incorrect\r
+results. In addition, it is clients should support close matches\r
+(aka "fuzzy" matches, or response code 211).\r
+\r
+The proper way to handle multiple fuzzy matches is to present the\r
+entire list of matches to the user and to let the user choose between them.\r
+Matches are listed in the order of best fit for the user's disc, so they\r
+should be presented to the user in the order they are listed by the server.\r
+\r
+The suggested algorithm for obtaining the list of server sites is\r
+as follows. The application should offer to get the list from\r
+freedb.freedb.org with the "sites" command the first time the user runs \r
+the program. Additionally the application should provide the user with \r
+some method of downloading the list on-demand.\r
+\r
+We do strongly suggest that you provide your users with the capability of\r
+choosing freedb server sites as described above. However, for some \r
+applications this may not be feasible. If you do not wish to offer this \r
+functionality, you may safely hard-code "freedb.freedb.org" in your \r
+application as the sole freedb site to access. This will deprive your users\r
+of the option to choose a site near their locale for optimal response, but\r
+that is your choice.\r
--- /dev/null
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <disk.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+/* acme */
+typedef struct Event Event;
+typedef struct Window Window;
+
+enum
+{
+ STACK = 16384,
+ EVENTSIZE = 256,
+ NEVENT = 5,
+};
+
+struct Event
+{
+ int c1;
+ int c2;
+ int q0;
+ int q1;
+ int flag;
+ int nb;
+ int nr;
+ char b[EVENTSIZE*UTFmax+1];
+ Rune r[EVENTSIZE+1];
+};
+
+struct Window
+{
+ /* file descriptors */
+ int ctl;
+ int event;
+ int addr;
+ int data;
+ Biobuf *body;
+
+ /* event input */
+ char buf[512];
+ char *bufp;
+ int nbuf;
+ Event e[NEVENT];
+
+ int id;
+ int open;
+ Channel *cevent; /* chan(Event*) */
+};
+
+extern Window* newwindow(void);
+extern int winopenfile(Window*, char*);
+extern void winopenbody(Window*, int);
+extern void winclosebody(Window*);
+extern void wintagwrite(Window*, char*, int);
+extern void winname(Window*, char*);
+extern void winwriteevent(Window*, Event*);
+extern void winread(Window*, uint, uint, char*);
+extern int windel(Window*, int);
+extern void wingetevent(Window*, Event*);
+extern void wineventproc(void*);
+extern void winwritebody(Window*, char*, int);
+extern void winclean(Window*);
+extern int winselect(Window*, char*, int);
+extern int winsetaddr(Window*, char*, int);
+extern char* winreadbody(Window*, int*);
+extern void windormant(Window*);
+extern void winsetdump(Window*, char*, char*);
+
+extern char* readfile(char*, char*, int*);
+extern void ctlprint(int, char*, ...);
+extern void* emalloc(uint);
+extern char* estrdup(char*);
+extern char* estrstrdup(char*, char*);
+extern char* egrow(char*, char*, char*);
+extern char* eappend(char*, char*, char*);
+extern void error(char*, ...);
+extern int tokenizec(char*, char**, int, char*);
+
+/* cd stuff */
+typedef struct Msf Msf; /* minute, second, frame */
+struct Msf {
+ int m;
+ int s;
+ int f;
+};
+
+typedef struct Track Track;
+struct Track {
+ Msf start;
+ Msf end;
+ ulong bstart;
+ ulong bend;
+ char *title;
+};
+
+enum {
+ MTRACK = 64,
+};
+typedef struct Toc Toc;
+struct Toc {
+ int ntrack;
+ int nchange;
+ int changetime;
+ int track0;
+ Track track[MTRACK];
+ char *title;
+};
+
+extern int msfconv(Fmt*);
+
+#pragma varargck argpos error 1
+#pragma varargck argpos ctlprint 2
+#pragma varargck type "M" Msf
+
+enum { /* state */
+ Sunknown,
+ Splaying,
+ Spaused,
+ Scompleted,
+ Serror,
+};
+
+typedef struct Cdstatus Cdstatus;
+struct Cdstatus {
+ int state;
+ int track;
+ int index;
+ Msf abs;
+ Msf rel;
+};
+
+typedef struct Drive Drive;
+struct Drive {
+ Window *w;
+ Channel *cstatus; /* chan(Cdstatus) */
+ Channel *ctocdisp; /* chan(Toc) */
+ Channel *cdbreq; /* chan(Toc) */
+ Channel *cdbreply; /* chan(Toc) */
+ Scsi *scsi;
+ Toc toc;
+ Cdstatus status;
+};
+
+int gettoc(Scsi*, Toc*);
+void drawtoc(Window*, Drive*, Toc*);
+void redrawtoc(Window*, Toc*);
+void tocproc(void*); /* Drive* */
+void cddbproc(void*); /* Drive* */
+void cdstatusproc(void*); /* Drive* */
+
+extern int debug;
+
+#define DPRINT if(debug)fprint
+void acmeevent(Drive*, Window*, Event*);
+
+int playtrack(Drive*, int, int);
+int pause(Drive*);
+int resume(Drive*);
+int stop(Drive*);
+int eject(Drive*);
+int ingest(Drive*);
+
+int markplay(Window*, ulong);
+int setplaytime(Window*, char*);
+void advancetrack(Drive*, Window*);
+
+
--- /dev/null
+#include "acd.h"
+
+static int
+iscmd(char *s, char *cmd)
+{
+ int len;
+
+ len = strlen(cmd);
+ return strncmp(s, cmd, len)==0 && (s[len]=='\0' || s[len]==' ' || s[len]=='\t' || s[len]=='\n');
+}
+
+static char*
+skip(char *s, char *cmd)
+{
+ s += strlen(cmd);
+ while(*s==' ' || *s=='\t' || *s=='\n')
+ s++;
+ return s;
+}
+
+//#define PLAYSTRING "/^[0-9:]+>"
+//#define PLAYSTRINGSPACE "/^[0-9:]+> ?"
+//#define INITSTRING "0:00> "
+
+#define INITSTRING "> "
+#define PLAYSTRING "/^>"
+#define PLAYSTRINGSPACE "/^> ?"
+
+/*
+ * find the playing string, leave in addr
+ * if q0, q1 are non-nil, set them to the addr of the string.
+ */
+int
+findplay(Window *w, char *s, ulong *q0, ulong *q1)
+{
+ char xbuf[25];
+ if(w->data < 0)
+ w->data = winopenfile(w, "data");
+
+ if(!winsetaddr(w, "#0", 1) || !winsetaddr(w, s, 1))
+ return 0;
+
+ seek(w->addr, 0, 0);
+ if(read(w->addr, xbuf, 24) != 24)
+ return 0;
+
+ xbuf[24] = 0;
+ if(q0)
+ *q0 = atoi(xbuf);
+ if(q1)
+ *q1 = atoi(xbuf+12);
+
+ return 1;
+}
+
+/*
+ * find the playing string and replace the time
+ */
+int
+setplaytime(Window *w, char *new)
+{
+ char buf[40];
+ ulong q0, q1;
+
+return 1;
+ if(!findplay(w, PLAYSTRING, &q0, &q1))
+ return 0;
+
+ q1--; /* > */
+ sprint(buf, "#%lud,#%lud", q0, q1);
+ DPRINT(2, "setaddr %s\n", buf);
+ if(!winsetaddr(w, buf, 1))
+ return 0;
+
+ if(write(w->data, new, strlen(new)) != strlen(new))
+ return 0;
+
+ return 1;
+}
+
+/*
+ * find the playing string, and remove it.
+ * return the string at the beginning of hte next line in buf
+ * (presumably a track number).
+ */
+static int
+unmarkplay(Window *w, char *buf, int n, ulong *q0, ulong *q1, ulong *qbegin)
+{
+ char xbuf[24];
+
+ if(!findplay(w, PLAYSTRINGSPACE, q0, q1))
+ return 0;
+
+ if(write(w->data, "", 0) < 0 || !winsetaddr(w, "+1+#0", 1))
+ return 0;
+
+ if(qbegin) {
+ seek(w->addr, 0, 0);
+ if(read(w->addr, xbuf, 24) != 24)
+ return 0;
+ *qbegin = atoi(xbuf);
+ }
+
+ if(buf) {
+ if((n = read(w->data, buf, n-1)) < 0)
+ return 0;
+
+ buf[n] = '\0';
+ }
+
+ return 1;
+}
+
+int
+markplay(Window *w, ulong q0)
+{
+ char buf[20];
+
+ if(w->data < 0)
+ w->data = winopenfile(w, "data");
+
+ sprint(buf, "#%lud", q0);
+ DPRINT(2, "addr %s\n", buf);
+ if(!winsetaddr(w, buf, 1) || !winsetaddr(w, "-0", 1))
+ return 0;
+ if(write(w->data, INITSTRING, strlen(INITSTRING)) != strlen(INITSTRING))
+ return 0;
+ return 1;
+}
+
+/* return 1 if handled, 0 otherwise */
+int
+cdcommand(Window *w, Drive *d, char *s)
+{
+ s = skip(s, "");
+
+ if(iscmd(s, "Del")){
+ if(windel(w, 0))
+ threadexitsall(nil);
+ return 1;
+ }
+ if(iscmd(s, "Stop")){
+ unmarkplay(w, nil, 0, nil, nil, nil);
+ stop(d);
+ return 1;
+ }
+ if(iscmd(s, "Eject")){
+ unmarkplay(w, nil, 0, nil, nil, nil);
+ eject(d);
+ return 1;
+ }
+ if(iscmd(s, "Ingest")){
+ unmarkplay(w, nil, 0, nil, nil, nil);
+ ingest(d);
+ return 1;
+ }
+ if(iscmd(s, "Pause")){
+ pause(d);
+ return 1;
+ }
+ if(iscmd(s, "Resume")){
+ resume(d);
+ return 1;
+ }
+ return 0;
+}
+
+void
+drawtoc(Window *w, Drive *d, Toc *t)
+{
+ int i, playing;
+
+ if(w->data < 0)
+ w->data = winopenfile(w, "data");
+ if(!winsetaddr(w, ",", 1))
+ return;
+
+ fprint(w->data, "Title\n\n");
+ playing = -1;
+ if(d->status.state == Splaying || d->status.state == Spaused)
+ playing = d->status.track-t->track0;
+
+ for(i=0; i<t->ntrack; i++)
+ fprint(w->data, "%s%d/ Track %d\n", i==playing ? "> " : "", i+1, i+1);
+ fprint(w->data, "");
+}
+
+void
+redrawtoc(Window *w, Toc *t)
+{
+ int i;
+ char old[50];
+
+ if(w->data < 0)
+ w->data = winopenfile(w, "data");
+ if(t->title) {
+ if(winsetaddr(w, "/Title", 1))
+ write(w->data, t->title, strlen(t->title));
+ }
+ for(i=0; i<t->ntrack; i++) {
+ if(t->track[i].title) {
+ sprint(old, "/Track %d", i+1);
+ if(winsetaddr(w, old, 1))
+ write(w->data, t->track[i].title, strlen(t->track[i].title));
+ }
+ }
+}
+
+void
+advancetrack(Drive *d, Window *w)
+{
+ int n;
+ ulong q0, q1, qnext;
+ char buf[20];
+
+ q0 = q1 = 0;
+ if(!unmarkplay(w, buf, sizeof(buf), &q0, &q1, &qnext)) {
+ DPRINT(2, "unmark: %r\n");
+ return;
+ }
+
+ DPRINT(2, "buf: %s\n", buf);
+ if(strncmp(buf, "repeat", 6) == 0) {
+ if(!winsetaddr(w, "#0", 1) || !findplay(w, "/^[0-9]+\\/", &qnext, nil)) {
+ DPRINT(2, "set/find: %r\n");
+ return;
+ }
+ if(w->data < 0)
+ w->data = winopenfile(w, "data");
+ if((n = read(w->data, buf, sizeof(buf)-1)) <= 0) {
+ DPRINT(2, "read %d: %r\n", n);
+ return;
+ }
+ buf[n] = 0;
+ DPRINT(2, "buf: %s\n", buf);
+ }
+
+ if((n = atoi(buf)) == 0)
+ return;
+
+ if(!markplay(w, qnext))
+ DPRINT(2, "err: %r");
+
+ playtrack(d, n-1, n-1);
+}
+
+void
+acmeevent(Drive *d, Window *w, Event *e)
+{
+ Event *ea, *e2, *eq;
+ char *s, *t, *buf;
+ int n, na;
+ ulong q0, q1;
+
+ switch(e->c1){ /* origin of action */
+ default:
+ Unknown:
+ fprint(2, "unknown message %c%c\n", e->c1, e->c2);
+ break;
+
+ case 'E': /* write to body or tag; can't affect us */
+ break;
+
+ case 'F': /* generated by our actions; ignore */
+ break;
+
+ case 'K': /* type away; we don't care */
+ break;
+
+ case 'M': /* mouse event */
+ switch(e->c2){ /* type of action */
+ case 'x': /* mouse: button 2 in tag */
+ case 'X': /* mouse: button 2 in body */
+ ea = nil;
+ // e2 = nil;
+ s = e->b;
+ if(e->flag & 2){ /* null string with non-null expansion */
+ e2 = recvp(w->cevent);
+ if(e->nb==0)
+ s = e2->b;
+ }
+ if(e->flag & 8){ /* chorded argument */
+ ea = recvp(w->cevent); /* argument */
+ na = ea->nb;
+ recvp(w->cevent); /* ignore origin */
+ }else
+ na = 0;
+
+ /* append chorded arguments */
+ if(na){
+ t = emalloc(strlen(s)+1+na+1);
+ sprint(t, "%s %s", s, ea->b);
+ s = t;
+ }
+ /* if it's a known command, do it */
+ /* if it's a long message, it can't be for us anyway */
+ DPRINT(2, "exec: %s\n", s);
+ if(!cdcommand(w, d, s)) /* send it back */
+ winwriteevent(w, e);
+ if(na)
+ free(s);
+ break;
+
+ case 'l': /* mouse: button 3 in tag */
+ case 'L': /* mouse: button 3 in body */
+ // buf = nil;
+ eq = e;
+ if(e->flag & 2){
+ e2 = recvp(w->cevent);
+ eq = e2;
+ }
+ s = eq->b;
+ if(eq->q1>eq->q0 && eq->nb==0){
+ buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
+ winread(w, eq->q0, eq->q1, buf);
+ s = buf;
+ }
+ DPRINT(2, "load %s\n", s);
+ if((n = atoi(s)) != 0) {
+ DPRINT(2, "mark %d\n", n);
+ q0 = q1 = 0;
+ unmarkplay(w, nil, 0, &q0, &q1, nil);
+
+ /* adjust eq->q* for deletion */
+ if(eq->q0 > q1) {
+ eq->q0 -= (q1-q0);
+ eq->q1 -= (q1-q0);
+ }
+ if(!markplay(w, eq->q0))
+ DPRINT(2, "err: %r\n");
+
+ playtrack(d, n-1, n-1);
+ } else
+ winwriteevent(w, e);
+ break;
+
+ case 'i': /* mouse: text inserted in tag */
+ case 'I': /* mouse: text inserted in body */
+ case 'd': /* mouse: text deleted from tag */
+ case 'D': /* mouse: text deleted from body */
+ break;
+
+ default:
+ goto Unknown;
+ }
+ }
+}
--- /dev/null
+<html><html>
+<head>
+<title>::freedb.org::</title>
+</head>
+
+<body bgcolor="#FFFFFF" text="#000000" link="#101070" vlink="#101070">
+
+<center>
+
+<table cellpadding=0 cellspacing=0 border=0 width="99%" align=center><tr><td align=left>
+<a href="/">
+<table border=0>
+<td bgcolor="#ffffff">
+ <table border=0 width=100% cellpadding=0 cellspacing=0>
+ <td bgcolor=#101070>
+ <table border=0>
+ <td bgcolor=#ffffff><font face="Arial,Helvetica,Lucida" color="#101070" size=8><b>freedb</b></td>
+ <td bgcolor=#101070><font face="Arial,Helvetica,Lucida" color="#ffffff" size=8><b>.org</b></td>
+ </table>
+ </td>
+ <tr>
+ <td align=right><font face="Arial,Helvetica,Lucida" color="#101070" size=2><b>a free approach to cddbp
+ </table>
+</td>
+</table>
+</a>
+</td><td align=right width=100%>
+ <form action="search.php" method=post>
+ <font face=Arial,Helvetica size=2><input type=name name=query width=20 size=20 length=20>
+ </td>
+ <td align=right> <input type=image src=images/menu/english/search.gif border=0 align=middle></td>
+ </form>
+
+</td></tr></table><br>
+<table cellpadding=0 cellspacing=0 border=0 width="99%" bgcolor=101070><tr><td>
+<table cellpadding=5 cellspacing=1 border=0 width="100%" bgcolor=FFFFFF><tr><td>
+<font face=Lucida,Verdana,Arial,Helvetica size=2>freedb.org - a free approach to cddbp</td></tr></table></td></tr></table><P>
+
+<table width="99%" align=center cellpadding=0 cellspacing=0 border=0><tr>
+ <td valign=top rowspan=5>
+
+<table border=0><tr><td>
+
+ <table width="115" border="0" cellpadding="0" cellspacing="0"><tr valign="top" bgcolor="#101070">
+ <td bgcolor="#FFFFFF"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
+ <td><img src="themes/SlashOcean/cl.gif" width="7" height="10"></td>
+ <td><font face="verdana,helvetica,arial" size="1" color="#ffffff"><B>Main Menu</B></font></td>
+ <td align="right"><img src="themes/SlashOcean/cr.gif" width="7" height="10" alt=""></td>
+ <td bgcolor="#FFFFFF" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
+ </tr></table>
+ <table width="100%" border="0" cellpadding="0" cellspacing="0">
+ <tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
+ <tr bgcolor="#ffffff">
+ <td background="themes/SlashOcean/sl.gif"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
+ <td width="100%">
+ <table width="100%" border="0" cellpadding="5" cellspacing="0"><tr>
+ <td><font face="verdana,helvetica,arial" size="1">
+ <li><a href=index.php>Home</a>\r
+<li><a href=topics.php>News-Topics</a>\r
+<li><a href=sections.php?op=listarticles&secid=1>About</a>\r
+<li><a href=sections.php?op=listarticles&secid=2>Developers</a>\r
+<li><a href=sections.php?op=listarticles&secid=3>Applications</a>\r
+<li><a href=sections.php?op=listarticles&secid=7>Download</a>\r
+<li><a href=forum/index.php>Forum</a>\r
+<li><a href=http://freedb.music.sk/search/>Web-based Search</a>\r
+<li><a href=links.php>Web Links</a>\r
+<li><a href=user.php>Your Account</a>\r
+<li><a href=submit.php>Submit News</a>\r
+\r
+ </font></td>
+ </tr></table>
+
+
+ </td><td background="themes/SlashOcean/sr.gif" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
+ </tr>
+ <tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
+ </table>
+ </td>
+
+
+
+
+
+
+</tr></td></table>
+<br>
+<table border=0><tr><td>
+
+ <table width="115" border="0" cellpadding="0" cellspacing="0"><tr valign="top" bgcolor="#101070">
+ <td bgcolor="#FFFFFF"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
+ <td><img src="themes/SlashOcean/cl.gif" width="7" height="10"></td>
+ <td><font face="verdana,helvetica,arial" size="1" color="#ffffff"><B>FAQ</B></font></td>
+ <td align="right"><img src="themes/SlashOcean/cr.gif" width="7" height="10" alt=""></td>
+ <td bgcolor="#FFFFFF" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
+ </tr></table>
+ <table width="100%" border="0" cellpadding="0" cellspacing="0">
+ <tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
+ <tr bgcolor="#ffffff">
+ <td background="themes/SlashOcean/sl.gif"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
+ <td width="100%">
+ <table width="100%" border="0" cellpadding="5" cellspacing="0"><tr>
+ <td><font face="verdana,helvetica,arial" size="1">
+ Our FAQ can be found <a href="http://freedb.freedb.org/sections.php?op=viewarticle&artid=26">here</a>.<br>\r
+Please read the FAQ before asking questions via email. </font></td>
+ </tr></table>
+
+
+ </td><td background="themes/SlashOcean/sr.gif" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
+ </tr>
+ <tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
+ </table>
+ </td>
+
+</tr></td></table>
+<br>
+<table border=0><tr><td>
+
+ <table width="115" border="0" cellpadding="0" cellspacing="0"><tr valign="top" bgcolor="#101070">
+ <td bgcolor="#FFFFFF"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
+ <td><img src="themes/SlashOcean/cl.gif" width="7" height="10"></td>
+ <td><font face="verdana,helvetica,arial" size="1" color="#ffffff"><B>Contact</B></font></td>
+ <td align="right"><img src="themes/SlashOcean/cr.gif" width="7" height="10" alt=""></td>
+ <td bgcolor="#FFFFFF" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
+ </tr></table>
+ <table width="100%" border="0" cellpadding="0" cellspacing="0">
+ <tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
+ <tr bgcolor="#ffffff">
+ <td background="themes/SlashOcean/sl.gif"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
+ <td width="100%">
+ <table width="100%" border="0" cellpadding="5" cellspacing="0"><tr>
+ <td><font face="verdana,helvetica,arial" size="1">
+ General questions:<br>\r
+<a href="mailto:info@freedb.org">info@freedb.org</a><hr>\r
+Databaseupdates:<br>\r
+<a href="mailto:updates@freedb.org">updates@freedb.org</a><br>\r
+(<b>NOT</b> for submission!)<hr>\r
+Please keep in mind that we are NOT the Nero-Support and please do not send CD-submits to the adresses above.<br>\r
+Submits have to go to:<br>\r
+<a href="mailto:freedb-submit@freedb.org">freedb-submit@freedb.org</a> </font></td>
+ </tr></table>
+
+
+ </td><td background="themes/SlashOcean/sr.gif" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
+ </tr>
+ <tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
+ </table>
+ </td>
+
+</tr></td></table>
+<br>
+<table border=0><tr><td>
+
+ <table width="115" border="0" cellpadding="0" cellspacing="0"><tr valign="top" bgcolor="#101070">
+ <td bgcolor="#FFFFFF"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
+ <td><img src="themes/SlashOcean/cl.gif" width="7" height="10"></td>
+ <td><font face="verdana,helvetica,arial" size="1" color="#ffffff"><B>Downloads</B></font></td>
+ <td align="right"><img src="themes/SlashOcean/cr.gif" width="7" height="10" alt=""></td>
+ <td bgcolor="#FFFFFF" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
+ </tr></table>
+ <table width="100%" border="0" cellpadding="0" cellspacing="0">
+ <tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
+ <tr bgcolor="#ffffff">
+ <td background="themes/SlashOcean/sl.gif"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
+ <td width="100%">
+ <table width="100%" border="0" cellpadding="5" cellspacing="0"><tr>
+ <td><font face="verdana,helvetica,arial" size="1">
+ The link to the database downloads is <a href="/sections.php?op=viewarticle&artid=12">here</a> </font></td>
+ </tr></table>
+
+
+ </td><td background="themes/SlashOcean/sr.gif" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
+ </tr>
+ <tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
+ </table>
+ </td>
+
+
+</tr></td></table>
+<td> </td><td valign="top" width="100%">
+
+<!-- columna de inicio -->
+<center>
+ <table border=0 cellpadding=1 cellspacing=0 width=100% bgcolor=000000><tr><td>
+ <table border=0 cellpadding=8 cellspacing=0 width=100% bgcolor=FFFFFF>
+ <tr><td align=left><font face=Arial,Helvetica size=3>
+ <b>Database-format specification</b><br>
+ <font size=2>
+ <br><br>
+ Due to problems with using backslashes on our Webpage, we cannot display the database-format specification directly here.<br>\r
+But you can find it <a href="http://www.freedb.org/software/old/DBFORMAT">here</a> as a text-document.
+ </tr></td>
+ <tr><td align=center><font face=Arial,Helvetica>
+
+ </tr></td>
+ </table></tr></td></table></center></td><td> </td>
+
+
+</tr></table></td></tr></table><br><br>
+
+<font face=Arial,Helvetica size=1><center>
+<br>
+<br>
+<br>
+<br>
+</body>
+</html>
--- /dev/null
+#include "acd.h"
+#include <ctype.h>
+
+/* see CDDBPROTO */
+static ulong
+cddb_sum(int n)
+{
+ int ret;
+ ret = 0;
+ while(n > 0) {
+ ret += n%10;
+ n /= 10;
+ }
+ return ret;
+}
+
+static ulong
+diskid(Toc *t)
+{
+ int i, n, tmp;
+ Msf *ms, *me;
+
+ n = 0;
+ for(i=0; i < t->ntrack; i++)
+ n += cddb_sum(t->track[i].start.m*60+t->track[i].start.s);
+
+ ms = &t->track[0].start;
+ me = &t->track[t->ntrack].start;
+ tmp = (me->m*60+me->s) - (ms->m*60+ms->s);
+
+ /*
+ * the spec says n%0xFF rather than n&0xFF. it's unclear which is correct.
+ * most CDs are in the database under both entries.
+ */
+ return ((n & 0xFF) << 24 | (tmp << 8) | t->ntrack);
+}
+
+static void
+append(char **d, char *s)
+{
+ char *r;
+ if (*d == nil)
+ *d = estrdup(s);
+ else {
+ r = emalloc(strlen(*d) + strlen(s) + 1);
+ strcpy(r, *d);
+ strcat(r, s);
+ free(*d);
+ *d = r;
+ }
+}
+
+static int
+cddbfilltoc(Toc *t)
+{
+ int fd;
+ int i;
+ char *p, *q;
+ Biobuf bin;
+ Msf *m;
+ char *f[10];
+ int nf;
+ char *id, *categ;
+ char gottrack[MTRACK];
+ int gottitle;
+
+ fd = dial("tcp!freedb.freedb.org!888", 0, 0, 0);
+ if(fd < 0) {
+ fprint(2, "cannot dial: %r\n");
+ return -1;
+ }
+ Binit(&bin, fd, OREAD);
+
+ if((p=Brdline(&bin, '\n')) == nil || atoi(p)/100 != 2) {
+ died:
+ close(fd);
+ Bterm(&bin);
+ fprint(2, "error talking to server\n");
+ if(p) {
+ p[Blinelen(&bin)-1] = 0;
+ fprint(2, "server says: %s\n", p);
+ }
+ return -1;
+ }
+
+ fprint(fd, "cddb hello gre plan9 9cd 1.0\r\n");
+ if((p = Brdline(&bin, '\n')) == nil || atoi(p)/100 != 2)
+ goto died;
+
+ fprint(fd, "cddb query %8.8lux %d", diskid(t), t->ntrack);
+ DPRINT(2, "cddb query %8.8lux %d", diskid(t), t->ntrack);
+ for(i=0; i<t->ntrack; i++) {
+ m = &t->track[i].start;
+ fprint(fd, " %d", (m->m*60+m->s)*75+m->f);
+ DPRINT(2, " %d", (m->m*60+m->s)*75+m->f);
+ }
+ m = &t->track[t->ntrack-1].end;
+ fprint(fd, " %d\r\n", m->m*60+m->s);
+ DPRINT(2, " %d\r\n", m->m*60+m->s);
+
+ if((p = Brdline(&bin, '\n')) == nil || atoi(p)/100 != 2)
+ goto died;
+ p[Blinelen(&bin)-1] = 0;
+ DPRINT(2, "cddb: %s\n", p);
+ nf = tokenize(p, f, nelem(f));
+ if(nf < 1)
+ goto died;
+
+ switch(atoi(f[0])) {
+ case 200: /* exact match */
+ if(nf < 3)
+ goto died;
+ categ = f[1];
+ id = f[2];
+ break;
+ case 211: /* close matches */
+ if((p = Brdline(&bin, '\n')) == nil)
+ goto died;
+ if(p[0] == '.') /* no close matches? */
+ goto died;
+ p[Blinelen(&bin)-1] = '\0';
+
+ /* accept first match */
+ nf = tokenize(p, f, nelem(f));
+ if(nf < 2)
+ goto died;
+ categ = f[0];
+ id = f[1];
+
+ /* snarf rest of buffer */
+ while(p[0] != '.') {
+ if((p = Brdline(&bin, '\n')) == nil)
+ goto died;
+ p[Blinelen(&bin)-1] = '\0';
+ DPRINT(2, "cddb: %s\n", p);
+ }
+ break;
+ case 202: /* no match */
+ default:
+ goto died;
+ }
+
+ /* fetch results for this cd */
+ fprint(fd, "cddb read %s %s\r\n", categ, id);
+
+ memset(gottrack, 0, sizeof(gottrack));
+ gottitle = 0;
+ do {
+ if((p = Brdline(&bin, '\n')) == nil)
+ goto died;
+ q = p+Blinelen(&bin)-1;
+ while(isspace(*q))
+ *q-- = 0;
+DPRINT(2, "cddb %s\n", p);
+ if(strncmp(p, "DTITLE=", 7) == 0) {
+ if (gottitle)
+ append(&t->title, p + 7);
+ else
+ t->title = estrdup(p+7);
+ gottitle = 1;
+ } else if(strncmp(p, "TTITLE", 6) == 0 && isdigit(p[6])) {
+ i = atoi(p+6);
+ if(i < t->ntrack) {
+ p += 6;
+ while(isdigit(*p))
+ p++;
+ if(*p == '=')
+ p++;
+
+ if (gottrack[i])
+ append(&t->track[i].title, p);
+ else
+ t->track[i].title = estrdup(p);
+ gottrack[i] = 1;
+ }
+ }
+ } while(*p != '.');
+
+ fprint(fd, "quit\r\n");
+ close(fd);
+ Bterm(&bin);
+
+ return 0;
+}
+
+void
+cddbproc(void *v)
+{
+ Drive *d;
+ Toc t;
+
+ threadsetname("cddbproc");
+ d = v;
+ while(recv(d->cdbreq, &t))
+ if(cddbfilltoc(&t) == 0)
+ send(d->cdbreply, &t);
+}
--- /dev/null
+<html><html>
+<head>
+<title>::freedb.org::</title>
+</head>
+
+<body bgcolor="#FFFFFF" text="#000000" link="#101070" vlink="#101070">
+
+<center>
+
+<table cellpadding=0 cellspacing=0 border=0 width="99%" align=center><tr><td align=left>
+<a href="/">
+<table border=0>
+<td bgcolor="#ffffff">
+ <table border=0 width=100% cellpadding=0 cellspacing=0>
+ <td bgcolor=#101070>
+ <table border=0>
+ <td bgcolor=#ffffff><font face="Arial,Helvetica,Lucida" color="#101070" size=8><b>freedb</b></td>
+ <td bgcolor=#101070><font face="Arial,Helvetica,Lucida" color="#ffffff" size=8><b>.org</b></td>
+ </table>
+ </td>
+ <tr>
+ <td align=right><font face="Arial,Helvetica,Lucida" color="#101070" size=2><b>a free approach to cddbp
+ </table>
+</td>
+</table>
+</a>
+</td><td align=right width=100%>
+ <form action="search.php" method=post>
+ <font face=Arial,Helvetica size=2><input type=name name=query width=20 size=20 length=20>
+ </td>
+ <td align=right> <input type=image src=images/menu/english/search.gif border=0 align=middle></td>
+ </form>
+
+</td></tr></table><br>
+<table cellpadding=0 cellspacing=0 border=0 width="99%" bgcolor=101070><tr><td>
+<table cellpadding=5 cellspacing=1 border=0 width="100%" bgcolor=FFFFFF><tr><td>
+<font face=Lucida,Verdana,Arial,Helvetica size=2>freedb.org - a free approach to cddbp</td></tr></table></td></tr></table><P>
+
+<table width="99%" align=center cellpadding=0 cellspacing=0 border=0><tr>
+ <td valign=top rowspan=5>
+
+<table border=0><tr><td>
+
+ <table width="115" border="0" cellpadding="0" cellspacing="0"><tr valign="top" bgcolor="#101070">
+ <td bgcolor="#FFFFFF"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
+ <td><img src="themes/SlashOcean/cl.gif" width="7" height="10"></td>
+ <td><font face="verdana,helvetica,arial" size="1" color="#ffffff"><B>Main Menu</B></font></td>
+ <td align="right"><img src="themes/SlashOcean/cr.gif" width="7" height="10" alt=""></td>
+ <td bgcolor="#FFFFFF" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
+ </tr></table>
+ <table width="100%" border="0" cellpadding="0" cellspacing="0">
+ <tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
+ <tr bgcolor="#ffffff">
+ <td background="themes/SlashOcean/sl.gif"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
+ <td width="100%">
+ <table width="100%" border="0" cellpadding="5" cellspacing="0"><tr>
+ <td><font face="verdana,helvetica,arial" size="1">
+ <li><a href=index.php>Home</a>\r
+<li><a href=topics.php>News-Topics</a>\r
+<li><a href=sections.php?op=listarticles&secid=1>About</a>\r
+<li><a href=sections.php?op=listarticles&secid=2>Developers</a>\r
+<li><a href=sections.php?op=listarticles&secid=3>Applications</a>\r
+<li><a href=sections.php?op=listarticles&secid=7>Download</a>\r
+<li><a href=forum/index.php>Forum</a>\r
+<li><a href=http://freedb.music.sk/search/>Web-based Search</a>\r
+<li><a href=links.php>Web Links</a>\r
+<li><a href=user.php>Your Account</a>\r
+<li><a href=submit.php>Submit News</a>\r
+\r
+ </font></td>
+ </tr></table>
+
+
+ </td><td background="themes/SlashOcean/sr.gif" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
+ </tr>
+ <tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
+ </table>
+ </td>
+
+
+
+
+
+
+</tr></td></table>
+<br>
+<table border=0><tr><td>
+
+ <table width="115" border="0" cellpadding="0" cellspacing="0"><tr valign="top" bgcolor="#101070">
+ <td bgcolor="#FFFFFF"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
+ <td><img src="themes/SlashOcean/cl.gif" width="7" height="10"></td>
+ <td><font face="verdana,helvetica,arial" size="1" color="#ffffff"><B>FAQ</B></font></td>
+ <td align="right"><img src="themes/SlashOcean/cr.gif" width="7" height="10" alt=""></td>
+ <td bgcolor="#FFFFFF" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
+ </tr></table>
+ <table width="100%" border="0" cellpadding="0" cellspacing="0">
+ <tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
+ <tr bgcolor="#ffffff">
+ <td background="themes/SlashOcean/sl.gif"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
+ <td width="100%">
+ <table width="100%" border="0" cellpadding="5" cellspacing="0"><tr>
+ <td><font face="verdana,helvetica,arial" size="1">
+ Our FAQ can be found <a href="http://freedb.freedb.org/sections.php?op=viewarticle&artid=26">here</a>.<br>\r
+Please read the FAQ before asking questions via email. </font></td>
+ </tr></table>
+
+
+ </td><td background="themes/SlashOcean/sr.gif" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
+ </tr>
+ <tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
+ </table>
+ </td>
+
+</tr></td></table>
+<br>
+<table border=0><tr><td>
+
+ <table width="115" border="0" cellpadding="0" cellspacing="0"><tr valign="top" bgcolor="#101070">
+ <td bgcolor="#FFFFFF"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
+ <td><img src="themes/SlashOcean/cl.gif" width="7" height="10"></td>
+ <td><font face="verdana,helvetica,arial" size="1" color="#ffffff"><B>Contact</B></font></td>
+ <td align="right"><img src="themes/SlashOcean/cr.gif" width="7" height="10" alt=""></td>
+ <td bgcolor="#FFFFFF" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
+ </tr></table>
+ <table width="100%" border="0" cellpadding="0" cellspacing="0">
+ <tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
+ <tr bgcolor="#ffffff">
+ <td background="themes/SlashOcean/sl.gif"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
+ <td width="100%">
+ <table width="100%" border="0" cellpadding="5" cellspacing="0"><tr>
+ <td><font face="verdana,helvetica,arial" size="1">
+ General questions:<br>\r
+<a href="mailto:info@freedb.org">info@freedb.org</a><hr>\r
+Databaseupdates:<br>\r
+<a href="mailto:updates@freedb.org">updates@freedb.org</a><br>\r
+(<b>NOT</b> for submission!)<hr>\r
+Please keep in mind that we are NOT the Nero-Support and please do not send CD-submits to the adresses above.<br>\r
+Submits have to go to:<br>\r
+<a href="mailto:freedb-submit@freedb.org">freedb-submit@freedb.org</a> </font></td>
+ </tr></table>
+
+
+ </td><td background="themes/SlashOcean/sr.gif" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
+ </tr>
+ <tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
+ </table>
+ </td>
+
+</tr></td></table>
+<br>
+<table border=0><tr><td>
+
+ <table width="115" border="0" cellpadding="0" cellspacing="0"><tr valign="top" bgcolor="#101070">
+ <td bgcolor="#FFFFFF"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
+ <td><img src="themes/SlashOcean/cl.gif" width="7" height="10"></td>
+ <td><font face="verdana,helvetica,arial" size="1" color="#ffffff"><B>Downloads</B></font></td>
+ <td align="right"><img src="themes/SlashOcean/cr.gif" width="7" height="10" alt=""></td>
+ <td bgcolor="#FFFFFF" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
+ </tr></table>
+ <table width="100%" border="0" cellpadding="0" cellspacing="0">
+ <tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
+ <tr bgcolor="#ffffff">
+ <td background="themes/SlashOcean/sl.gif"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
+ <td width="100%">
+ <table width="100%" border="0" cellpadding="5" cellspacing="0"><tr>
+ <td><font face="verdana,helvetica,arial" size="1">
+ The link to the database downloads is <a href="/sections.php?op=viewarticle&artid=12">here</a> </font></td>
+ </tr></table>
+
+
+ </td><td background="themes/SlashOcean/sr.gif" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
+ </tr>
+ <tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
+ </table>
+ </td>
+
+
+</tr></td></table>
+<td> </td><td valign="top" width="100%">
+
+<!-- columna de inicio -->
+<center>
+ <table border=0 cellpadding=1 cellspacing=0 width=100% bgcolor=000000><tr><td>
+ <table border=0 cellpadding=8 cellspacing=0 width=100% bgcolor=FFFFFF>
+ <tr><td align=left><font face=Arial,Helvetica size=3>
+ <b>CDDB-protocol documentation</b><br>
+ <font size=2>
+ <br><br>
+ <pre>\r
+ CDDB Protocol\r
+\r
+ By Steve Scherf and Ti Kan\r
+ --------------------------\r
+\r
+Revision: $Id: CDDBPROTO,v 1.6 1997/05/14 07:53:52 steve Exp steve $\r
+\r
+\r
+Notation:\r
+-> : client to server\r
+<- : server to client\r
+\r
+terminating marker: `.' character in the beginning of a line\r
+\r
+\r
+Server response code (three digit code):\r
+\r
+First digit:\r
+1xx Informative message\r
+2xx Command OK\r
+3xx Command OK so far, continue\r
+4xx Command OK, but cannot be performed for some specified reasons\r
+5xx Command unimplemented, incorrect, or program error\r
+ \r
+Second digit:\r
+x0x Ready for further commands\r
+x1x More server-to-client output follows (until terminating marker)\r
+x2x More client-to-server input follows (until terminating marker)\r
+x3x Connection will close\r
+\r
+Third digit:\r
+xx[0-9] Command-specific code\r
+\r
+\r
+CDDB Protocol Level 1:\r
+----------------------\r
+\r
+Server sign-on banner:\r
+----------------------\r
+<- code hostname CDDBP server version ready at date\r
+\r
+ code:\r
+ 200 OK, read/write allowed\r
+ 201 OK, read only\r
+ 432 No connections allowed: permission denied\r
+ 433 No connections allowed: X users allowed, Y currently active\r
+ 434 No connections allowed: system load too high\r
+ hostname:\r
+ Server host name. Example: xyz.fubar.com\r
+ version:\r
+ Version number of server software. Example: v1.0PL0\r
+ date:\r
+ Current date and time. Example: Wed Mar 13 00:41:34 1996\r
+\r
+\r
+Initial client-server handshake:\r
+--------------------------------\r
+Note: This handshake must occur before other cddb commands\r
+ are accepted by the server.\r
+\r
+Client command:\r
+-> cddb hello username hostname clientname version\r
+\r
+ username:\r
+ Login name of user. Example: johndoe\r
+ hostname:\r
+ Host name of client. Example: abc.fubar.com\r
+ clientname:\r
+ The name of the connecting client. Example: xmcd, cda, EasyCD,\r
+ et cetera. Do not use the name of another client which already\r
+ exists.\r
+ version:\r
+ Version number of client software. Example: v1.0PL0\r
+\r
+Server response:\r
+<- code hello and welcome username@hostname running clientname version\r
+\r
+ code:\r
+ 200 Handshake successful\r
+ 431 Handshake not successful, closing connection\r
+ 402 Already shook hands\r
+\r
+\r
+CDDB query:\r
+-----------\r
+Client command:\r
+-> cddb query discid ntrks off1 off2 ... nsecs\r
+\r
+ discid:\r
+ CD disc ID number. Example: f50a3b13\r
+ ntrks:\r
+ Total number of tracks on CD.\r
+ off1, off2, ...:\r
+ Frame offset of the starting location of each track.\r
+ nsecs:\r
+ Total playing length of CD in seconds.\r
+\r
+Server response:\r
+<- code categ discid dtitle\r
+ or\r
+<- code close matches found\r
+<- categ discid dtitle\r
+<- categ discid dtitle\r
+<- (more matches...)\r
+<- .\r
+\r
+ code:\r
+ 200 Found exact match\r
+ 211 Found inexact matches, list follows (until terminating marker)\r
+ 202 No match found\r
+ 403 Database entry is corrupt\r
+ 409 No handshake\r
+ categ:\r
+ CD category. Example: rock\r
+ discid:\r
+ CD disc ID number of the found entry. Example: f50a3b13\r
+ dtitle:\r
+ The Disc Artist and Disc Title (The DTITLE line). For example:\r
+ Pink Floyd / The Dark Side of the Moon\r
+\r
+\r
+CDDB read:\r
+----------\r
+Client command:\r
+-> cddb read categ discid\r
+\r
+ categ:\r
+ CD category. Example: rock\r
+ discid:\r
+ CD disc ID number. Example: f50a3b13\r
+\r
+Server response:\r
+<- code categ discid\r
+<- # xmcd 2.0 CD database file\r
+<- # ...\r
+<- (CDDB data...)\r
+<- .\r
+ or\r
+<- code categ discid No such CD entry in database\r
+\r
+ code:\r
+ 210 OK, CDDB database entry follows (until terminating marker)\r
+ 401 Specified CDDB entry not found.\r
+ 402 Server error.\r
+ 403 Database entry is corrupt.\r
+ 409 No handshake.\r
+ categ:\r
+ CD category. Example: rock\r
+ discid:\r
+ CD disc ID number. Example: f50a3b13\r
+\r
+\r
+CDDB search: (command not yet implemented in freedb-serversoftware!)\r
+------------\r
+Client command:\r
+-> cddb srch key search_type ... search_type\r
+\r
+ key:\r
+ Pseudo-regular expression to match. Expressions should meet the\r
+ following description:\r
+\r
+ - No white space.\r
+ - Printable characters only.\r
+ - Case is ignored.\r
+ search_type:\r
+ CDDB fields to search through. Example: title\r
+ Supported types: artist, title, extd, ext, trk\r
+ categ:\r
+ CD category. Example: rock\r
+\r
+Server response:\r
+<- code matches found\r
+<- categ discid dtitle\r
+<- categ discid dtitle\r
+<- (more matches...)\r
+<- .\r
+\r
+ code:\r
+ 210 OK, matches found, list follows (until terminating marker)\r
+ 401 No match found.\r
+ 409 No handshake.\r
+ categ:\r
+ CD category. Example: rock\r
+ dtitle:\r
+ The Disc Artist and Disc Title (The DTITLE line). For example:\r
+ Pink Floyd / The Dark Side of the Moon\r
+\r
+\r
+CDDB write:\r
+-----------\r
+Client command:\r
+-> cddb write categ discid\r
+\r
+ categ:\r
+ CD category. Example: rock\r
+ discid:\r
+ CD disc ID number. Example: f50a3b13\r
+\r
+Server response:\r
+<- code categ discid\r
+\r
+ code:\r
+ 320 OK, input CDDB data (until terminating marker)\r
+ 401 Permission denied.\r
+ 402 Server file system full/file access failed.\r
+ 409 No handshake.\r
+ 501 Entry rejected: reason for rejection.\r
+ categ:\r
+ CD category. Example: rock\r
+ discid:\r
+ CD disc ID number. Example: f50a3b13\r
+\r
+Client data:\r
+-> # xmcd 2.0 CD database file\r
+-> # ...\r
+-> (CDDB data)\r
+-> .\r
+\r
+Server response:\r
+<- code message\r
+\r
+ code:\r
+ 200 CDDB entry accepted\r
+ 401 CDDB entry rejected: reason why\r
+ message:\r
+ Message string to indicate write status:\r
+ CDDB entry accepted, or CDDB entry rejected.\r
+\r
+\r
+Help information:\r
+-----------------\r
+Client command:\r
+-> help\r
+ or\r
+-> help cmd\r
+\r
+ cmd:\r
+ CDDB command. Example: quit\r
+\r
+ or\r
+\r
+-> help cmd subcmd\r
+\r
+ cmd:\r
+ CDDB command. Example: cddb\r
+ subcmd:\r
+ CDDB command argument. Example: query\r
+\r
+Server response:\r
+<- code Help information follows\r
+<- (help data ...)\r
+<- .\r
+ or\r
+<- code no help information available\r
+\r
+ code:\r
+ 210 OK, help information follows (until terminating marker)\r
+ 401 No help information available\r
+\r
+\r
+Log statistics:\r
+---------------\r
+Client command:\r
+-> log [[-l lines] [start date [end date]] | [day [days]] | [get"]]\r
+\r
+ lines:\r
+ The maximum number of lines to print for each data list in the\r
+ log statistics.\r
+ start date:\r
+ The date after which statistics should be calculated. Date is\r
+ of the format: hh[mm[ss[MM[DD[[CC]YY]]]]]\r
+\r
+ E.g.: 201200053196 for 8:12 PM on May 31, 1996.\r
+ 20120005312096 for 8:12 PM on May 31, 2096.\r
+ 080530 for today at at 8:15 and 30 seconds.\r
+\r
+ If the century ("CC") is omitted, a reasonable guess is made. If\r
+ this argument is omitted, all messages are considered.\r
+ end date:\r
+ The date after which statistics should not be calculated. If\r
+ omitted, the end date is assumed to be the current date.\r
+ day:\r
+ The string "day". This solitary argument will cause a log search\r
+ of messages generated within the last day.\r
+ days:\r
+ A positive numerical argument which modifies the number of days'\r
+ messages to searh. If this argument is left out, the default is 1.\r
+ get:\r
+ The string "get". This solitary argument will cause the server\r
+ to send the contents of the log file.\r
+\r
+Server response:\r
+<- code Log summary follows\r
+<- (log stats)\r
+<- .\r
+ or\r
+<- code Log follows\r
+<- (log stats)\r
+<- .\r
+\r
+ code:\r
+ 210 OK, log summary follows (until terminating marker)\r
+ 211 OK, log follows (until terminating marker)\r
+ 401 Permission denied\r
+ 402 No log information available\r
+ 501 Invalid start/end date\r
+\r
+\r
+Message of the day:\r
+------------------\r
+Client command:\r
+-> motd\r
+\r
+Server response:\r
+<- code Last modified: date MOTD follows (until terminating marker)\r
+<- (message text)\r
+<- .\r
+\r
+ code:\r
+ 210 Last modified: 05/31/96 06:31:14 MOTD follows (until terminating marker)\r
+ 401 No message of the day available\r
+ date:\r
+ The date the text of the message of the day was modified. The date\r
+ appears in the following format:\r
+\r
+ 05/31/96 06:31:14\r
+\r
+ This value may be used by client software as a message timestamp\r
+ for purposes of determining if it has already been displayed. This\r
+ format was chosen because it is more easily parsed than the standard\r
+ ctime() format.\r
+\r
+\r
+Server protocol level:\r
+----------------------\r
+Client command:\r
+-> proto [level]\r
+\r
+ level:\r
+ The (numerical) protocol level to set the server to.\r
+\r
+Server response:\r
+<- code CDDB protocol level: current cur_level, supported supported_level\r
+ or\r
+<- code OK, protocol version now: cur_level\r
+\r
+ code:\r
+ 200 CDDB protocol level: current cur_level, supported supp_level\r
+ 201 OK, protocol version now: cur_level\r
+ 501 Illegal protocol level.\r
+ 502 Protocol level already cur_level.\r
+ cur_level:\r
+ The current protocol level at which the server is running.\r
+ supported_level:\r
+ The maximum supported protocol level.\r
+\r
+\r
+Server sites:\r
+--------------\r
+Client command:\r
+-> sites\r
+\r
+Server response:\r
+<- code OK, site information follows (until terminating `.')\r
+<- (data)\r
+<- .\r
+\r
+ code:\r
+ 210 Ok, site information follows\r
+ 401 No site information available.\r
+\r
+ The data format is as follows:\r
+ site port latitude longitude description\r
+\r
+ The fields are as follows:\r
+ site:\r
+ The Internet address of the remote site.\r
+ port:\r
+ The port at which the server resides on that site.\r
+ latitude:\r
+ The latitude of the server site. The format is as follows:\r
+ CDDD.MM\r
+ Where "C" is the compass direction (N, S), "DDD" is the\r
+ degrees, and "MM" is the minutes.\r
+ longitude:\r
+ The longitude of the server site. Format is as above, except\r
+ the compass direction must be one of (E, W).\r
+ description:\r
+ A short description of the geographical location of the site.\r
+\r
+ Example:\r
+ cddb.moonsoft.com 888 N037.23 W122.01 Fremont, CA USA\r
+\r
+\r
+Server status:\r
+--------------\r
+Client command:\r
+-> stat\r
+\r
+Server response:\r
+<- code OK, status information follows (until terminating `.')\r
+<- (data)\r
+<- .\r
+\r
+ code:\r
+ 210 Ok, status information follows\r
+\r
+ The possible data is as follows:\r
+ current proto: <current_level>\r
+ An integer representing the server's current operating protocol\r
+ level.\r
+ max proto: <max_level>\r
+ The maximum supported protocol level.\r
+ gets: <yes | no>\r
+ Whether or not the client is allowed to get log information,\r
+ according to the string "yes" or "no".\r
+ updates: <yes | no>\r
+ Whether or not the client is allowed to initiate a database\r
+ update, according to the string "yes" or "no".\r
+ posting: <yes | no>\r
+ Whether or not the client is allowed to post new entries,\r
+ according to the string "yes" or "no".\r
+ quotes: <yes | no>\r
+ Whether or not quoted arguments are enabled, according to\r
+ the string "yes" or "no".\r
+ current users: <num_users>\r
+ The number of users currently connected to the server.\r
+ max users: <num_max_users>\r
+ The number of users that can concurrently connect to the server.\r
+ strip ext: <yes | no>\r
+ Whether or not extended data is stripped by the server before\r
+ presented to the user.\r
+ Database entries: <num_db_entries>\r
+ The total number of entries in the database.\r
+ Database entries by category:\r
+ This field is followed by a list of catgories and the number\r
+ of entries in that category. Each entry is of the following\r
+ format:\r
+\r
+ <white space>catgory: <num_db_entries>\r
+\r
+ The list of entries is terminated by the first line that does\r
+ not begin with white space.\r
+\r
+ Pending file transmissions:\r
+ This field is followed by a list of sites that are fed new\r
+ database entries at periodic intervals, and the number of\r
+ entries that have yet to be transmitted to that site.\r
+ Each entry is of the following format:\r
+\r
+ <white space>site: <num_db_entries>\r
+\r
+ The list of entries is terminated by the first line that does\r
+ not begin with white space.\r
+\r
+ This list may grow as needed, so clients must expect possible\r
+ unrecognizable data. Also, additional fields may be added to\r
+ the currently existing lines, although no existing fields will\r
+ be removed or change position.\r
+ \r
+\r
+Server version:\r
+---------------\r
+Client command:\r
+-> ver\r
+\r
+Server response:\r
+<- code servername version copyright\r
+ or\r
+<- code Version information follows\r
+\r
+ code:\r
+ 200 Version information.\r
+ 211 OK, version information follows (until terminating marker)\r
+ version:\r
+ Server version. Example: v1.0PL0\r
+ copyright:\r
+ Copyright string. Example: Copyright (c) 1996 Steve Scherf\r
+\r
+\r
+Database update:\r
+----------------\r
+Client command:\r
+-> update\r
+\r
+Server response:\r
+<- code Updating the database.\r
+ or\r
+<- code Permission denied.\r
+ or\r
+<- code Unable to update the database.\r
+\r
+ code:\r
+ 200 Updating the database.\r
+ 401 Permission denied.\r
+ 402 Unable to update the database.\r
+\r
+\r
+Server users:\r
+-------------\r
+Client command:\r
+-> whom\r
+\r
+Server response:\r
+<- code User list follows\r
+\r
+ code:\r
+ 210 OK, user list follows (until terminating marker)\r
+ 401 No user information available.\r
+\r
+\r
+Client sign-off:\r
+----------------\r
+Client command:\r
+-> quit\r
+\r
+Server response:\r
+<- code hostname closing connection. Goodbye.\r
+\r
+ code:\r
+ 230 OK, goodbye.\r
+ hostname:\r
+ Server host name. Example: xyz.fubar.com\r
+\r
+\r
+General errors:\r
+---------------\r
+\r
+Server response:\r
+<- code error\r
+ code:\r
+ 402 Server error.\r
+ 408 CGI environment error.\r
+ 500 Command syntax error, command unknown, command unimplemented.\r
+ 530 Server error, server timeout.\r
+\r
+\r
+Reserved errors:\r
+----------------\r
+\r
+The following error codes are reserved, and will never be returned as a\r
+response to a CDDB protocol command. They are intended to be used internally\r
+by clients that have a need for generating pseudo-responses.\r
+\r
+ 600-699\r
+\r
+\r
+CDDB Protocol Level 2:\r
+----------------------\r
+\r
+In all respects, protocol level 2 is the same as level 1, with the exceptions\r
+listed below.\r
+\r
+Arguments to commands may be surrounded by double quotes. All characters\r
+within the quotes, including white space, are included in the argument. All\r
+white space is replaced by the `_' (2Dh) character by the server. White space\r
+is defined as ` ' (20h) and `^I' (control-I, or 09h).\r
+\r
+Arguments containing quotes that should not be interpreted with the special\r
+meaning described above should be escaped with a preceding backslash character,\r
+or '' (5Ch). If an actual backslash appears in an argument, it should be\r
+escaped with a preceding backslash. In both cases, the preceding backslash\r
+will be removed from the input before being interpreted.\r
+\r
+\r
+CDDB Protocol Level 3:\r
+----------------------\r
+\r
+Protocol level 3 is the same as level 2, with the exception listed below.\r
+\r
+The output of the "sites" command has changed to meet the folowing description:\r
+\r
+ The data format is as follows:\r
+ site protocol port address latitude longitude description\r
+\r
+ The fields are as follows:\r
+ site:\r
+ The Internet address of the remote site.\r
+ protocol:\r
+ The transfer protocol used to access the site.\r
+ port:\r
+ The port at which the server resides on that site.\r
+ address:\r
+ Any additional addressing information needed to access the\r
+ server. For example, for HTTP protocol servers, this would be\r
+ the path to the CDDB server CGI script. This field will be\r
+ "-" if no additional addressing information is needed.\r
+ latitude:\r
+ The latitude of the server site. The format is as follows:\r
+ CDDD.MM\r
+ Where "C" is the compass direction (N, S), "DDD" is the\r
+ degrees, and "MM" is the minutes.\r
+ longitude:\r
+ The longitude of the server site. Format is as above, except\r
+ the compass direction must be one of (E, W).\r
+ description:\r
+ A short description of the geographical location of the site.\r
+\r
+ Example:\r
+ cddb.moonsoft.com cddbp 888 - N037.23 W122.01 Fremont, CA USA\r
+ cddb.moonsoft.com http 80 /~cddb/cddb.cgi N037.23 W122.01 Fremont,CA USA\r
+\r
+Note that a site may appear once for each type of protocol it supports for\r
+accessing the server.\r
+\r
+\r
+Addendum A: Proper use of CDDBP:\r
+--------------------------------\r
+\r
+There are a few guidelines that must be followed in order to make proper use\r
+of CDDBP:\r
+\r
+- When handshaking with the server via the "cddb hello" command, the client\r
+ must specify its own name and version, not that of some other client (such\r
+ as xmcd). Also, the "username" and "hostname" must be that of the actual\r
+ user running the program, not some hardwired value.\r
+\r
+- Before performing a "cddb read", the client program MUST perform a\r
+ "cddb query". Failure to do so may result in the client program receiving\r
+ incorrect CDDB data from the server. Also, without performing a query, the\r
+ client program will not benefit from close matches in the event of the\r
+ lack of an exact match in the database.\r
+\r
+- For accounting purposes, it is best if client programs only perform a single\r
+ "cddb query" for a particular disc before performing a "cddb read" for that\r
+ disc.\r
+\r
+\r
+Addendum B: CDDBP under HTTP:\r
+-----------------------------\r
+\r
+Accessing a server as a CGI script is done in much the same way as through\r
+direct interaction. The command set is identical, though the method of\r
+communication is through CDDBP commands encapsulated in the HTTP protocol.\r
+The only limitation is that a single command may be executed per connection,\r
+since HTTP is not truly interactive. For the server to be accessed in this\r
+way, it must reside on the target host at a known URL which is accessible by\r
+the host HTTP server. The client program must connect to the HTTP server on\r
+the target host and issue an HTTP command with the appropriate CDDBP command\r
+encapsulated within.\r
+\r
+Commands may be submitted to servers in CGI mode using either the "GET" or\r
+"POST" HTTP commands. Both methods are supported, and there is no real\r
+difference between how both are to be used other than the syntactical\r
+difference between the two methods. The "POST" method may provide the ability\r
+to issue longer commands, though, depending on the architecture of the system\r
+on which the server resides.\r
+\r
+The server command must be sent as part of the "Request-URI" in the case\r
+of the "GET" method, and as the "Entity-Body" in the case of the "POST"\r
+method. In both cases, the command must be of the following form:\r
+\r
+cmd=server+command&hello=joe+my.host.com+clientname+version&proto=1\r
+\r
+Where the text following the "cmd=" represents the CDDBP command to be\r
+executed, the text following the "hello=" represents the arguments to\r
+the "cddb hello" command that is implied by this operation, and the\r
+text following the "proto=" represents the argument to the "proto" command\r
+that is implied by this operation.\r
+\r
+The "+" characters in the input represent spaces, and will be translated\r
+by the server before performing the request. Special characters may be\r
+represented by the sequence "%XX" where "XX" is a two-digit hex number\r
+corresponding to the ASCII (ISO-8859-1) sequence of that character. The\r
+"&" characters denote separations between the command, hello and proto\r
+arguments. Newlines and carriage returns must not appear anywhere in the\r
+string except at the end.\r
+\r
+All CDDBP commands are supported under HTTP, except for "cddb hello",\r
+"cddb write", "proto" and "quit".\r
+\r
+For example, should user "joe" on system "my.host.com" be running xmcd 2.1,\r
+a read request for his currenly playing CD might look like this:\r
+\r
+cmd=cddb+read+rock+12345678&hello=joe+my.host.com+xmcd+2.1&proto=1\r
+\r
+The server will perform the implied "proto" and "cddb hello" commands,\r
+and then perform the requested "cddb read" command.\r
+\r
+Server response to the command is encapsulated in the HTTP server response,\r
+and appears in the "Entity-Body" exactly as it would appear using the CDDBP\r
+protocol. Note that the HTTP response "Entity-Header" is not guaranteed to\r
+contain a "Content-Length" field, so clients should be prepared to accept\r
+variable length input. This is no different from operation under CDDBP. The\r
+header will always contain a Mime "Content-Type" field which describes the\r
+body of data as "text/plain".\r
+\r
+For more detailed information on HTTP and Mime, see RFC 1945 and RFC 1521.\r
+</pre>
+ </tr></td>
+ <tr><td align=center><font face=Arial,Helvetica>
+
+ </tr></td>
+ </table></tr></td></table></center></td><td> </td>
+
+
+</tr></table></td></tr></table><br><br>
+
+<font face=Arial,Helvetica size=1><center>
+<br>
+<br>
+<br>
+<br>
+</body>
+</html>
--- /dev/null
+\r
+CDDB DISCID\r
+-----------\r
+\r
+Both forms of CDDB access require that the software compute a "disc\r
+ID" which is an identifier that is used to access the CDDB. The disc\r
+ID is a 8-digit hexadecimal (base-16) number, computed using data from\r
+a CD's Table-of-Contents (TOC) in MSF (Minute Second Frame) form. The\r
+algorithm is listed below in Appendix A.\r
+\r
+It is crucial that your software compute the disc ID correctly. If it\r
+does not generate the correct disc ID, it will not be compatible with CDDB.\r
+Moreover, if your software submits CDDB entries with bad disc IDs to the\r
+CDDB archives, it could compromise the integrity of the CDDB.\r
+\r
+[...]\r
+\r
+APPENDIX A - CDDB DISCID ALGORITHM\r
+----------------------------------\r
+\r
+The following is a C code example that illustrates how to generate the\r
+CDDB disc ID. [...] A text description\r
+of the algorithm follows, which should contain the necessary information\r
+to code the algorithm in any programming language.\r
+\r
+\r
+struct toc {\r
+ int min;\r
+ int sec;\r
+ int frame;\r
+};\r
+\r
+struct toc cdtoc[100];\r
+\r
+int\r
+read_cdtoc_from_drive(void)\r
+{\r
+ /* Do whatever is appropriate to read the TOC of the CD\r
+ * into the cdtoc[] structure array.\r
+ */\r
+ return (tot_trks);\r
+}\r
+\r
+int\r
+cddb_sum(int n)\r
+{\r
+ int ret;\r
+\r
+ /* For backward compatibility this algorithm must not change */\r
+\r
+ ret = 0;\r
+\r
+ while (n > 0) {\r
+ ret = ret + (n % 10);\r
+ n = n / 10;\r
+ }\r
+\r
+ return (ret);\r
+}\r
+\r
+unsigned long\r
+cddb_discid(int tot_trks)\r
+{\r
+ int i,\r
+ t = 0,\r
+ n = 0;\r
+\r
+ /* For backward compatibility this algorithm must not change */\r
+\r
+ i = 0;\r
+\r
+ while (i < tot_trks) {\r
+ n = n + cddb_sum((cdtoc[i].min * 60) + cdtoc[i].sec);\r
+ i++;\r
+ }\r
+\r
+ t = ((cdtoc[tot_trks].min * 60) + cdtoc[tot_trks].sec) -\r
+ ((cdtoc[0].min * 60) + cdtoc[0].sec);\r
+\r
+ return ((n % 0xff) << 24 | t << 8 | tot_trks);\r
+}\r
+\r
+main()\r
+{\r
+ int tot_trks;\r
+\r
+ tot_trks = read_cdtoc_from_drive();\r
+ printf("The discid is %08x", cddb_discid(tot_trks));\r
+}\r
+\r
+\r
+This code assumes that your compiler and architecture support 32-bit\r
+integers.\r
+\r
+The cddb_discid function computes the discid based on the CD's TOC data\r
+in MSF form. The frames are ignored for this purpose. The function is\r
+passed a parameter of tot_trks (which is the total number of tracks on\r
+the CD), and returns the discid integer number.\r
+\r
+It is assumed that cdtoc[] is an array of data structures (records)\r
+containing the fields min, sec and frame, which are the minute, second\r
+and frame offsets (the starting location) of each track. This\r
+information is read from the TOC of the CD. There are actually\r
+tot_trks + 1 "active" elements in the array, the last one being the\r
+offset of the lead-out (also known as track 0xAA).\r
+\r
+The function loops through each track in the TOC, and for each track\r
+it takes the (M * 60) + S (total offset in seconds) of the track and\r
+feeds it to cddb_sum() function, which simply adds the value of each digit\r
+in the decimal string representation of the number. A running sum of this\r
+result for each track is kept in the variable n.\r
+\r
+At the end of the loop:\r
+1. t is calculated by subtracting the (M * 60) + S offset of the lead-out\r
+minus the (M * 60) + S offset of first track (yielding the length of\r
+the disc in seconds).\r
+\r
+2. The result of (n modulo FFh) is left-shifted by 24 bits.\r
+\r
+3. t is left shifted by 8.\r
+\r
+The bitwise-OR operation of result 2., 3. and the tot_trks number is\r
+used as the discid.\r
+\r
+The discid is represented in hexadecimal form for the purpose of\r
+xmcd cddb file names and the DISCID= field in the xmcd cddb file itself.\r
+If the hexadecimal string is less than 8 characters long, it is\r
+zero-padded to 8 characters (i.e., 3a8f07 becomes 003a8f07). All\r
+alpha characters in the string should be in lower case, where\r
+applicable.\r
+\r
+Important note for clients using the MS-Windows MCI interface:\r
+\r
+The Windows MCI interface does not provide the MSF location of the\r
+lead-out. Thus, you must compute the lead-out location by taking the\r
+starting position of the last track and add the length of the last track\r
+to it. However, the MCI interface returns the length of the last track\r
+as ONE FRAME SHORT of the actual length found in the CD's TOC. In most\r
+cases this does not affect the disc ID generated, because we truncate\r
+the frame count when computing the disc ID anyway. However, if the\r
+lead-out track has an actual a frame count of 0, the computed quantity\r
+(based on the MSF data returned from the MCI interface) would result in\r
+the seconds being one short and the frame count be 74. For example,\r
+a CD with the last track at an offset of 48m 32s 12f and having a\r
+track length of 2m 50s 63f has a lead-out offset of 51m 23s 0f. Windows\r
+MCI incorrectly reports the length as 2m 50s 62f, which would yield a\r
+lead-out offset of 51m 22s 74f, which causes the resulting truncated\r
+disc length to be off by one second. This will cause an incorrect disc\r
+ID to be generated. You should thus add one frame to the length of the\r
+last track when computing the location of the lead-out.\r
+\r
+The easiest way for Windows clients to compute the lead-out given information\r
+in MSF format is like this:\r
+\r
+(offset_minutes * 60 * 75) + (offset_seconds * 75) + offset_frames +\r
+(length_minutes * 60 * 75) + (length_seconds * 75) + length_frames + 1 = X\r
+\r
+Where X is the offset of the lead-out in frames. To find the lead-out in\r
+seconds, simply divide by 75 and discard the remainder.\r
--- /dev/null
+<html><html>
+<head>
+<title>::freedb.org::</title>
+</head>
+
+<body bgcolor="#FFFFFF" text="#000000" link="#101070" vlink="#101070">
+
+<center>
+
+<table cellpadding=0 cellspacing=0 border=0 width="99%" align=center><tr><td align=left>
+<a href="/">
+<table border=0>
+<td bgcolor="#ffffff">
+ <table border=0 width=100% cellpadding=0 cellspacing=0>
+ <td bgcolor=#101070>
+ <table border=0>
+ <td bgcolor=#ffffff><font face="Arial,Helvetica,Lucida" color="#101070" size=8><b>freedb</b></td>
+ <td bgcolor=#101070><font face="Arial,Helvetica,Lucida" color="#ffffff" size=8><b>.org</b></td>
+ </table>
+ </td>
+ <tr>
+ <td align=right><font face="Arial,Helvetica,Lucida" color="#101070" size=2><b>a free approach to cddbp
+ </table>
+</td>
+</table>
+</a>
+</td><td align=right width=100%>
+ <form action="search.php" method=post>
+ <font face=Arial,Helvetica size=2><input type=name name=query width=20 size=20 length=20>
+ </td>
+ <td align=right> <input type=image src=images/menu/english/search.gif border=0 align=middle></td>
+ </form>
+
+</td></tr></table><br>
+<table cellpadding=0 cellspacing=0 border=0 width="99%" bgcolor=101070><tr><td>
+<table cellpadding=5 cellspacing=1 border=0 width="100%" bgcolor=FFFFFF><tr><td>
+<font face=Lucida,Verdana,Arial,Helvetica size=2>freedb.org - a free approach to cddbp</td></tr></table></td></tr></table><P>
+
+<table width="99%" align=center cellpadding=0 cellspacing=0 border=0><tr>
+ <td valign=top rowspan=5>
+
+<table border=0><tr><td>
+
+ <table width="115" border="0" cellpadding="0" cellspacing="0"><tr valign="top" bgcolor="#101070">
+ <td bgcolor="#FFFFFF"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
+ <td><img src="themes/SlashOcean/cl.gif" width="7" height="10"></td>
+ <td><font face="verdana,helvetica,arial" size="1" color="#ffffff"><B>Main Menu</B></font></td>
+ <td align="right"><img src="themes/SlashOcean/cr.gif" width="7" height="10" alt=""></td>
+ <td bgcolor="#FFFFFF" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
+ </tr></table>
+ <table width="100%" border="0" cellpadding="0" cellspacing="0">
+ <tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
+ <tr bgcolor="#ffffff">
+ <td background="themes/SlashOcean/sl.gif"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
+ <td width="100%">
+ <table width="100%" border="0" cellpadding="5" cellspacing="0"><tr>
+ <td><font face="verdana,helvetica,arial" size="1">
+ <li><a href=index.php>Home</a>\r
+<li><a href=topics.php>News-Topics</a>\r
+<li><a href=sections.php?op=listarticles&secid=1>About</a>\r
+<li><a href=sections.php?op=listarticles&secid=2>Developers</a>\r
+<li><a href=sections.php?op=listarticles&secid=3>Applications</a>\r
+<li><a href=sections.php?op=listarticles&secid=7>Download</a>\r
+<li><a href=forum/index.php>Forum</a>\r
+<li><a href=http://freedb.music.sk/search/>Web-based Search</a>\r
+<li><a href=links.php>Web Links</a>\r
+<li><a href=user.php>Your Account</a>\r
+<li><a href=submit.php>Submit News</a>\r
+\r
+ </font></td>
+ </tr></table>
+
+
+ </td><td background="themes/SlashOcean/sr.gif" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
+ </tr>
+ <tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
+ </table>
+ </td>
+
+
+
+
+
+
+</tr></td></table>
+<br>
+<table border=0><tr><td>
+
+ <table width="115" border="0" cellpadding="0" cellspacing="0"><tr valign="top" bgcolor="#101070">
+ <td bgcolor="#FFFFFF"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
+ <td><img src="themes/SlashOcean/cl.gif" width="7" height="10"></td>
+ <td><font face="verdana,helvetica,arial" size="1" color="#ffffff"><B>FAQ</B></font></td>
+ <td align="right"><img src="themes/SlashOcean/cr.gif" width="7" height="10" alt=""></td>
+ <td bgcolor="#FFFFFF" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
+ </tr></table>
+ <table width="100%" border="0" cellpadding="0" cellspacing="0">
+ <tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
+ <tr bgcolor="#ffffff">
+ <td background="themes/SlashOcean/sl.gif"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
+ <td width="100%">
+ <table width="100%" border="0" cellpadding="5" cellspacing="0"><tr>
+ <td><font face="verdana,helvetica,arial" size="1">
+ Our FAQ can be found <a href="http://freedb.freedb.org/sections.php?op=viewarticle&artid=26">here</a>.<br>\r
+Please read the FAQ before asking questions via email. </font></td>
+ </tr></table>
+
+
+ </td><td background="themes/SlashOcean/sr.gif" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
+ </tr>
+ <tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
+ </table>
+ </td>
+
+</tr></td></table>
+<br>
+<table border=0><tr><td>
+
+ <table width="115" border="0" cellpadding="0" cellspacing="0"><tr valign="top" bgcolor="#101070">
+ <td bgcolor="#FFFFFF"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
+ <td><img src="themes/SlashOcean/cl.gif" width="7" height="10"></td>
+ <td><font face="verdana,helvetica,arial" size="1" color="#ffffff"><B>Contact</B></font></td>
+ <td align="right"><img src="themes/SlashOcean/cr.gif" width="7" height="10" alt=""></td>
+ <td bgcolor="#FFFFFF" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
+ </tr></table>
+ <table width="100%" border="0" cellpadding="0" cellspacing="0">
+ <tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
+ <tr bgcolor="#ffffff">
+ <td background="themes/SlashOcean/sl.gif"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
+ <td width="100%">
+ <table width="100%" border="0" cellpadding="5" cellspacing="0"><tr>
+ <td><font face="verdana,helvetica,arial" size="1">
+ General questions:<br>\r
+<a href="mailto:info@freedb.org">info@freedb.org</a><hr>\r
+Databaseupdates:<br>\r
+<a href="mailto:updates@freedb.org">updates@freedb.org</a><br>\r
+(<b>NOT</b> for submission!)<hr>\r
+Please keep in mind that we are NOT the Nero-Support and please do not send CD-submits to the adresses above.<br>\r
+Submits have to go to:<br>\r
+<a href="mailto:freedb-submit@freedb.org">freedb-submit@freedb.org</a> </font></td>
+ </tr></table>
+
+
+ </td><td background="themes/SlashOcean/sr.gif" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
+ </tr>
+ <tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
+ </table>
+ </td>
+
+</tr></td></table>
+<br>
+<table border=0><tr><td>
+
+ <table width="115" border="0" cellpadding="0" cellspacing="0"><tr valign="top" bgcolor="#101070">
+ <td bgcolor="#FFFFFF"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
+ <td><img src="themes/SlashOcean/cl.gif" width="7" height="10"></td>
+ <td><font face="verdana,helvetica,arial" size="1" color="#ffffff"><B>Downloads</B></font></td>
+ <td align="right"><img src="themes/SlashOcean/cr.gif" width="7" height="10" alt=""></td>
+ <td bgcolor="#FFFFFF" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
+ </tr></table>
+ <table width="100%" border="0" cellpadding="0" cellspacing="0">
+ <tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
+ <tr bgcolor="#ffffff">
+ <td background="themes/SlashOcean/sl.gif"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
+ <td width="100%">
+ <table width="100%" border="0" cellpadding="5" cellspacing="0"><tr>
+ <td><font face="verdana,helvetica,arial" size="1">
+ The link to the database downloads is <a href="/sections.php?op=viewarticle&artid=12">here</a> </font></td>
+ </tr></table>
+
+
+ </td><td background="themes/SlashOcean/sr.gif" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
+ </tr>
+ <tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
+ </table>
+ </td>
+
+
+</tr></td></table>
+<td> </td><td valign="top" width="100%">
+
+<!-- columna de inicio -->
+<center>
+ <table border=0 cellpadding=1 cellspacing=0 width=100% bgcolor=000000><tr><td>
+ <table border=0 cellpadding=8 cellspacing=0 width=100% bgcolor=FFFFFF>
+ <tr><td align=left><font face=Arial,Helvetica size=3>
+ <b>Mailinglists</b><br>
+ <font size=2>
+ <br><br>
+ There are a couple of mailinglists available:\r
+\r
+<h4>fdb-apps@freedb.org</h4>\r
+\r
+This mailinglist is intended for all developers that want to\r
+exchange tips and tricks, codesnippets and so on. Subcribe\r
+to this list by sending an email to<br>\r
+<a href="mailto:fdb-apps-request@freedb.org">fdb-apps-request@freedb.org</a><br>\r
+with the word "subscribe" in the body of the email.\r
+<h4>fdb-dev@freedb.org</h4>\r
+\r
+This list is for anyone interested in developing the freedb.org\r
+server software. Subcribe\r
+to this list by sending an email to<br>\r
+<a href="mailto:fdb-dev-request@freedb.org">fdb-dev-request@freedb.org</a><br>\r
+with the word "subscribe" in the body of the email.
+ </tr></td>
+ <tr><td align=center><font face=Arial,Helvetica>
+
+ </tr></td>
+ </table></tr></td></table></center></td><td> </td>
+
+
+</tr></table></td></tr></table><br><br>
+
+<font face=Arial,Helvetica size=1><center>
+<br>
+<br>
+<br>
+<br>
+</body>
+</html>
--- /dev/null
+#include "acd.h"
+
+int debug;
+
+void
+usage(void)
+{
+ fprint(2, "usage: acd dev\n");
+ threadexitsall("usage");
+}
+
+Alt
+mkalt(Channel *c, void *v, int op)
+{
+ Alt a;
+
+ memset(&a, 0, sizeof(a));
+ a.c = c;
+ a.v = v;
+ a.op = op;
+ return a;
+}
+
+void
+freetoc(Toc *t)
+{
+ int i;
+
+ free(t->title);
+ for(i=0; i<t->ntrack; i++)
+ free(t->track[i].title);
+}
+
+void
+eventwatcher(Drive *d)
+{
+ enum { STATUS, WEVENT, TOCDISP, DBREQ, DBREPLY, NALT };
+ Alt alts[NALT+1];
+ Toc nt, tdb;
+ Event *e;
+ Window *w;
+ Cdstatus s;
+ char buf[40];
+
+ w = d->w;
+
+ alts[STATUS] = mkalt(d->cstatus, &s, CHANRCV);
+ alts[WEVENT] = mkalt(w->cevent, &e, CHANRCV);
+ alts[TOCDISP] = mkalt(d->ctocdisp, &nt, CHANRCV);
+ alts[DBREQ] = mkalt(d->cdbreq, &tdb, CHANNOP);
+ alts[DBREPLY] = mkalt(d->cdbreply, &nt, CHANRCV);
+ alts[NALT] = mkalt(nil, nil, CHANEND);
+ for(;;) {
+ switch(alt(alts)) {
+ case STATUS:
+ //DPRINT(2, "s...");
+ d->status = s;
+ if(s.state == Scompleted) {
+ s.state = Sunknown;
+ advancetrack(d, w);
+ }
+ //DPRINT(2, "status %d %d %d %M %M\n", s.state, s.track, s.index, s.abs, s.rel);
+ sprint(buf, "%d:%2.2d", s.rel.m, s.rel.s);
+ setplaytime(w, buf);
+ break;
+ case WEVENT:
+ //DPRINT(2, "w...");
+ acmeevent(d, w, e);
+ break;
+ case TOCDISP:
+ //DPRINT(2,"td...");
+ freetoc(&d->toc);
+ d->toc = nt;
+ drawtoc(w, d, &d->toc);
+ tdb = nt;
+ alts[DBREQ].op = CHANSND;
+ break;
+ case DBREQ: /* sent */
+ //DPRINT(2,"dreq...");
+ alts[DBREQ].op = CHANNOP;
+ break;
+ case DBREPLY:
+ //DPRINT(2,"drep...");
+ freetoc(&d->toc);
+ d->toc = nt;
+ redrawtoc(w, &d->toc);
+ break;
+ }
+ }
+}
+
+void
+threadmain(int argc, char **argv)
+{
+ Scsi *s;
+ Drive *d;
+ char buf[80];
+
+ ARGBEGIN{
+ case 'v':
+ debug++;
+ scsiverbose++;
+ }ARGEND
+
+ if(argc != 1)
+ usage();
+
+ fmtinstall('M', msfconv);
+
+ if((s = openscsi(argv[0])) == nil)
+ error("opening scsi: %r");
+
+ d = malloc(sizeof(*d));
+ if(d == nil)
+ error("out of memory");
+ memset(d, 0, sizeof d);
+
+ d->scsi = s;
+ d->w = newwindow();
+ d->ctocdisp = chancreate(sizeof(Toc), 0);
+ d->cdbreq = chancreate(sizeof(Toc), 0);
+ d->cdbreply = chancreate(sizeof(Toc), 0);
+ d->cstatus = chancreate(sizeof(Cdstatus), 0);
+
+ proccreate(wineventproc, d->w, STACK);
+ proccreate(cddbproc, d, STACK);
+ proccreate(cdstatusproc, d, STACK);
+
+ cleanname(argv[0]);
+ snprint(buf, sizeof(buf), "%s/", argv[0]);
+ winname(d->w, buf);
+
+ wintagwrite(d->w, "Stop Pause Resume Eject Ingest ", 5+6+7+6+7);
+ eventwatcher(d);
+}
--- /dev/null
+</$objtype/mkfile
+
+TARG=acd
+BIN=/acme/bin/$objtype
+
+OFILES=\
+ acme.$O\
+ cddb.$O\
+ main.$O\
+ mmc.$O\
+ util.$O\
+ win.$O\
+
+HFILES=acd.h
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+ ${TARG:%=/acme/bin/386/%}\
+
+</sys/src/cmd/mkone
--- /dev/null
+#include "acd.h"
+
+int
+msfconv(Fmt *fp)
+{
+ Msf m;
+
+ m = va_arg(fp->args, Msf);
+ fmtprint(fp, "%d.%d.%d", m.m, m.s, m.f);
+ return 0;
+}
+
+static int
+status(Drive *d)
+{
+ uchar cmd[12];
+
+ memset(cmd, 0, sizeof cmd);
+ cmd[0] = 0xBD;
+ return scsi(d->scsi, cmd, sizeof cmd, nil, 0, Snone);
+}
+
+static int
+playmsf(Drive *d, Msf start, Msf end)
+{
+ uchar cmd[12];
+
+ memset(cmd, 0, sizeof cmd);
+ cmd[0] = 0x47;
+ cmd[3] = start.m;
+ cmd[4] = start.s;
+ cmd[5] = start.f;
+ cmd[6] = end.m;
+ cmd[7] = end.s;
+ cmd[8] = end.f;
+
+ return scsi(d->scsi, cmd, sizeof cmd, nil, 0, Snone);
+}
+
+int
+playtrack(Drive *d, int start, int end)
+{
+ Toc *t;
+
+ t = &d->toc;
+
+ if(t->ntrack == 0)
+ return -1;
+
+ if(start < 0)
+ start = 0;
+ if(end >= t->ntrack)
+ end = t->ntrack-1;
+ if(end < start)
+ end = start;
+
+ return playmsf(d, t->track[start].start, t->track[end].end);
+}
+
+int
+resume(Drive *d)
+{
+ uchar cmd[12];
+
+ memset(cmd, 0, sizeof cmd);
+ cmd[0] = 0x4B;
+ cmd[8] = 0x01;
+ return scsi(d->scsi, cmd, sizeof cmd, nil, 0, Snone);
+}
+
+int
+pause(Drive *d)
+{
+ uchar cmd[12];
+
+ memset(cmd, 0, sizeof cmd);
+ cmd[0] = 0x4B;
+ return scsi(d->scsi, cmd, sizeof cmd, nil, 0, Snone);
+}
+
+int
+stop(Drive *d)
+{
+ uchar cmd[12];
+
+ memset(cmd, 0, sizeof cmd);
+ cmd[0] = 0x4E;
+ return scsi(d->scsi, cmd, sizeof cmd, nil, 0, Snone);
+}
+
+int
+eject(Drive *d)
+{
+ uchar cmd[12];
+
+ memset(cmd, 0, sizeof cmd);
+ cmd[0] = 0x1B;
+ cmd[1] = 1;
+ cmd[4] = 2;
+ return scsi(d->scsi, cmd, sizeof cmd, nil, 0, Snone);
+}
+
+int
+ingest(Drive *d)
+{
+ uchar cmd[12];
+
+ memset(cmd, 0, sizeof cmd);
+ cmd[0] = 0x1B;
+ cmd[1] = 1;
+ cmd[4] = 3;
+ return scsi(d->scsi, cmd, sizeof cmd, nil, 0, Snone);
+}
+
+static Msf
+rdmsf(uchar *p)
+{
+ Msf msf;
+
+ msf.m = p[0];
+ msf.s = p[1];
+ msf.f = p[2];
+ return msf;
+}
+
+static ulong
+rdlba(uchar *p)
+{
+ return (p[0]<<16) | (p[1]<<8) | p[2];
+}
+
+/* not a Drive, so that we don't accidentally touch Drive.toc */
+int
+gettoc(Scsi *s, Toc *t)
+{
+ int i, n;
+ uchar cmd[12];
+ uchar resp[1024];
+
+Again:
+ memset(t, 0, sizeof(*t));
+ memset(cmd, 0, sizeof cmd);
+ cmd[0] = 0x43;
+ cmd[1] = 0x02;
+ cmd[7] = sizeof(resp)>>8;
+ cmd[8] = sizeof(resp);
+
+ s->changetime = 1;
+ /* scsi sets nchange, changetime */
+ if(scsi(s, cmd, sizeof cmd, resp, sizeof(resp), Sread) < 4)
+ return -1;
+
+ if(s->changetime == 0) {
+ t->ntrack = 0;
+ werrstr("no media");
+ return -1;
+ }
+
+ if(t->nchange == s->nchange && t->changetime != 0)
+ return 0;
+
+ t->nchange = s->nchange;
+ t->changetime = s->changetime;
+
+ if(t->ntrack > MTRACK)
+ t->ntrack = MTRACK;
+
+DPRINT(2, "%d %d\n", resp[3], resp[2]);
+ t->ntrack = resp[3]-resp[2]+1;
+ t->track0 = resp[2];
+
+ n = ((resp[0]<<8) | resp[1])+2;
+ if(n < 4+8*(t->ntrack+1)) {
+ werrstr("bad read0 %d %d", n, 4+8*(t->ntrack+1));
+ return -1;
+ }
+
+ for(i=0; i<=t->ntrack; i++) /* <=: track[ntrack] = end */
+ t->track[i].start = rdmsf(resp+4+i*8+5);
+
+ for(i=0; i<t->ntrack; i++)
+ t->track[i].end = t->track[i+1].start;
+
+ memset(cmd, 0, sizeof cmd);
+ cmd[0] = 0x43;
+ cmd[7] = sizeof(resp)>>8;
+ cmd[8] = sizeof(resp);
+ if(scsi(s, cmd, sizeof cmd, resp, sizeof(resp), Sread) < 4)
+ return -1;
+
+ if(s->changetime != t->changetime || s->nchange != t->nchange) {
+ fprint(2, "disk changed underfoot; repeating\n");
+ goto Again;
+ }
+
+ n = ((resp[0]<<8) | resp[1])+2;
+ if(n < 4+8*(t->ntrack+1)) {
+ werrstr("bad read");
+ return -1;
+ }
+
+ for(i=0; i<=t->ntrack; i++)
+ t->track[i].bstart = rdlba(resp+4+i*8+5);
+
+ for(i=0; i<t->ntrack; i++)
+ t->track[i].bend = t->track[i+1].bstart;
+
+ return 0;
+}
+
+static void
+dumptoc(Toc *t)
+{
+ int i;
+
+ fprint(1, "%d tracks\n", t->ntrack);
+ for(i=0; i<t->ntrack; i++)
+ print("%d. %M-%M (%lud-%lud)\n", i+1,
+ t->track[i].start, t->track[i].end,
+ t->track[i].bstart, t->track[i].bend);
+}
+
+static void
+ping(Drive *d)
+{
+ uchar cmd[12];
+
+ memset(cmd, 0, sizeof cmd);
+ cmd[0] = 0x43;
+ scsi(d->scsi, cmd, sizeof(cmd), nil, 0, Snone);
+}
+
+static int
+playstatus(Drive *d, Cdstatus *stat)
+{
+ uchar cmd[12], resp[16];
+
+ memset(cmd, 0, sizeof cmd);
+ cmd[0] = 0x42;
+ cmd[1] = 0x02;
+ cmd[2] = 0x40;
+ cmd[3] = 0x01;
+ cmd[7] = sizeof(resp)>>8;
+ cmd[8] = sizeof(resp);
+ if(scsi(d->scsi, cmd, sizeof(cmd), resp, sizeof(resp), Sread) < 0)
+ return -1;
+
+ switch(resp[1]){
+ case 0x11:
+ stat->state = Splaying;
+ break;
+ case 0x12:
+ stat->state = Spaused;
+ break;
+ case 0x13:
+ stat->state = Scompleted;
+ break;
+ case 0x14:
+ stat->state = Serror;
+ break;
+ case 0x00: /* not supported */
+ case 0x15: /* no current status to return */
+ default:
+ stat->state = Sunknown;
+ break;
+ }
+
+ stat->track = resp[6];
+ stat->index = resp[7];
+ stat->abs = rdmsf(resp+9);
+ stat->rel = rdmsf(resp+13);
+ return 0;
+}
+
+void
+cdstatusproc(void *v)
+{
+ Drive *d;
+ Toc t;
+ Cdstatus s;
+
+ t.changetime = ~0;
+ t.nchange = ~0;
+
+ threadsetname("cdstatusproc");
+ d = v;
+ DPRINT(2, "cdstatus %d\n", getpid());
+ for(;;) {
+ ping(d);
+ //DPRINT(2, "d %d %d t %d %d\n", d->scsi->changetime, d->scsi->nchange, t.changetime, t.nchange);
+ if(playstatus(d, &s) == 0)
+ send(d->cstatus, &s);
+ if(d->scsi->changetime != t.changetime || d->scsi->nchange != t.nchange) {
+ if(gettoc(d->scsi, &t) == 0) {
+ DPRINT(2, "sendtoc...\n");
+ if(debug) dumptoc(&t);
+ send(d->ctocdisp, &t);
+ } else
+ DPRINT(2, "error: %r\n");
+ }
+ sleep(1000);
+ }
+}
--- /dev/null
+acd is composed of four procs
+
+wineventproc (win.c:/^wineventproc)
+ reads acme window events, sends them along w->cevent.
+
+cdstatusproc (mmc.c:/^cdstatusproc)
+ reads cd status once per second, sending
+ status updates to d->cstatus.
+ detects disk changes, sends new tocs to d->ctocdisp.
+
+cddbproc (cddb.c:/^cddbproc)
+ reads tocs from d->cdbreq, if it finds
+ translations in the cddb, sends new tocs to d->cdbreply.
+
+eventwatcher (main.c:/^eventwatcher)
+ the main event loop.
+ reads status from d->cstatus.
+ reads events from w->cevent.
+ reads new tocs to display from d->ctocdisp.
+ sends new tocs to translate to d->cdbreq.
+ reads new translated tocs from d->cdbreply.
+
+an interesting bug in the original design:
+ both cdstatusproc and the eventwatcher proc
+ issue scsi commands. (the eventwatcher responds to
+ things such as Play, Stop, etc., as well as advancing the track.)
+
+ the sd(3) driver did not expect overlapped commands,
+ and crashed.
+
+ this has been fixed by making the scsi(2) commands threadsafe,
+ and making the sd(3) driver more robust.
--- /dev/null
+CDDB SUBMISSION\r
+---------------\r
+\r
+Your software may allow users to enter CDDB data and then submit them\r
+to the freedb archive. \r
+There are two methods of submission: <a href="#email">via e-mail</a> or <a href="#http">via http</a> using submit.cgi\r
+\r
+<a name="email"></a>1. Submission via e-mail\r
+------------------------\r
+\r
+Your software has to send the entry to the\r
+following address:\r
+\r
+ freedb-submit@freedb.org\r
+\r
+You may implement a button or somesuch in your software's user-interface\r
+to facilitate this. The destination e-mail address should be made\r
+user-configurable.\r
+\r
+There should be one e-mail message per freedb entry. The mail Subject\r
+line should be in the form "cddb category discid". For example:\r
+\r
+Subject: cddb rock 850f970b\r
+\r
+The body of the e-mail message should be in the format of a CDDB file\r
+entry as described <a href="http://freedb.freedb.org/software/old/DBFORMAT">here</a>. The messages should contain only\r
+plain ASCII text. Do not attach encoded information or add special\r
+escape sequences.\r
+\r
+Note that the disc ID specified in the mail Subject line should\r
+also appear in the list of disc IDs in the DISCID= field of the\r
+CDDB file entry. If not, it is considered an error and the submission\r
+will be rejected.\r
+\r
+You should only allow categories that are currently supported by the\r
+freedb (blues, classical, country, data, folk, jazz, misc, newage,\r
+reggae, rock, soundtrack). Submissions specifying unsupported\r
+categories will be rejected.\r
+\r
+Please do not allow a user to submit CD database entries that\r
+have completely unfilled contents (i.e., blank information in the\r
+disc artist/title as well as the track titles, or filled with\r
+useless default information like "track 1", "track 2", etc.).\r
+While the current CD database server checks and rejects submissions\r
+that have a blank DTITLE line, it doesn't (and can't feasibly) check\r
+the track titles effectively, nor can it check any of these fields\r
+if they are filled with a default string. If it were, it would\r
+have to be hacked to know about the default strings of every possible\r
+client.\r
+\r
+Thus, please design your client with this in mind. This is a somewhat\r
+tricky thing to do, as some CDs contain blank tracks with no titles\r
+and you need to allow for that. An example minimum requirement\r
+that a CD player client should meet is listed below:\r
+\r
+1. Don't allow the "send" or "submit" feature to be activated if\r
+ the CD database information form is not edited at all.\r
+2. Check that the disc artist/title contains something (that the user\r
+ typed in).\r
+3. Check that all of the tracks have a title filled in by the user \r
+ (some (but not all!) may be blank, but not the default string).\r
+\r
+This should minimize the number of useless garbage being submitted\r
+into the CD database.\r
+\r
+Before you release your software, please be sure that it produces\r
+submissions that adheres to the CDDB file format, and that the frame\r
+offset, disc length, and disc ID information are correctly computed.\r
+For testing, please make your software send submissions to the\r
+following e-mail address (rather than the real submission site at\r
+freedb-submit@freedb.org):\r
+\r
+ test-submit@freedb.org\r
+\r
+The test address performs sanity checking on the CDDB submission and\r
+sends back pass/fail confirmation, but does not actually deposit the\r
+entry in the CD database.\r
+\r
+<a name="http"></a>2. Submission via http\r
+----------------------\r
+\r
+For submit via http, your application has to transmit the entry to the\r
+database through a CGI program at the following URL:\r
+\r
+http://freedb.freedb.org/~cddb/submit.cgi\r
+\r
+Submissions are made through the CGI program as follows. You must only use\r
+the "POST" method of sending data; "GET" is not supported. There are several\r
+HTTP "Entity-Header" fields that must be included in the data followed by a\r
+blank line, followed by the "Entity-Body" (a.k.a the CDDB entry) in the\r
+format described in Appendix B below. The required header fields are:\r
+\r
+Category: CDDB_category\r
+Discid: CDDB_discid\r
+User-Email: user@domain\r
+Submit-Mode: test_or_submit\r
+Content-Length: length_of_CDDB_entry\r
+\r
+Where:\r
+\r
+- "CDDB_category" is one of the valid CDDB categories (blues, classical,\r
+ country, data, folk, jazz, misc, newage, reggae, rock, soundtrack).\r
+ Invalid categories will result in the entry being rejected.\r
+\r
+- "CDDB_discid" is the 8-digit hex CDDB disc ID of the entry as described in\r
+ the "<a href="http://freedb.freedb.org/sections.php?op=viewarticle&artid=6">Discid howto</a>" section. This must be the same disc ID that appears\r
+ in the "DISCID=" section of the entry being submitted. If not, the entry\r
+ will be rejected.\r
+\r
+- "user@domain" is the valid email address of the user submitting the entry.\r
+ This is required in case a submission failure notice must be sent to the\r
+ user.\r
+\r
+- "test_or_submit" is the word "test" or "submit" (without the surrounding\r
+ quotes) to indicate whether the submission is a test submission or a real\r
+ submission to the database, respectively. See <a href="#testsubmission">below</a> for an explanation of\r
+ test submissions.\r
+\r
+- "length_of_CDDB_entry" is the size in bytes of the CDDB entry being\r
+ submitted. This number does not include the length of the header or the\r
+ blank line separating the HTTP header and the CDDB entry.\r
+\r
+There are several additional optional HTTP header fields that may also\r
+be specified (but which are currently not used by the freedb):\r
+\r
+Charset: character_set_of_CDDB_entry\r
+X-Cddbd-Note: message for user\r
+\r
+Where:\r
+\r
+- "character_set_of_CDDB_entry" is one of ISO-8859-1 or US-ASCII (lower case\r
+ may be used if desired). This specifies to the CDDB server which character\r
+ set the CDDB entry has been encoded in. If your application knows the\r
+ user's character set, then you should specify it here. Only these two\r
+ character sets are supported currently. DO NOT specify the character set\r
+ if your application does not have any way of verifying the user's character\r
+ set (i.e. do not guess; it's better not to specify it at all).\r
+\r
+- "message for user" is an arbitrary message to be included at the top of\r
+ any rejection notice that may be sent to the submitting user.\r
+\r
+An example submission showing the HTTP command, "Entity-Header" and "Entity-\r
+Body" follows:\r
+\r
+POST /~cddb/submit.cgi HTTP/1.0\r
+Category: rock\r
+Discid: 2a09310a\r
+User-Email: joe@joeshost.joesdomain.com\r
+Submit-Mode: submit\r
+Charset: ISO-8859-1\r
+X-Cddbd-Note: Problems with Super CD Player? Send email to support@supercd.com.\r
+Content-Length: 820\r
+\r
+# xmcd\r
+#\r
+# Track frame offsets:\r
+[ data omitted in this example for brevity ]\r
+PLAYORDER=\r
+\r
+Note the blank line between the "Content-Length" header field and the\r
+"# xmcd" which marks the beginning of the CDDB entry.\r
+\r
+When your application submits an entry through the CGI program, it will\r
+respond with a 3-digit response code indicating whether or not the entry has\r
+been forwarded to the freedb server for inclusion in the database, followed\r
+by a textual description of the response code. For example:\r
+\r
+200 OK, submission has been sent.\r
+400 Internal error: failed to forward submission.\r
+500 Missing required header information.\r
+\r
+These are but a few of the possible responses. \r
+See the description of the <a href="http://freedb.freedb.org/sections.php?op=viewarticle&artid=28">CDDB server protocol</a> for more information on \r
+handling response codes.\r
+\r
+The body of the freedb entry being submitted should be sent verbatim as\r
+described in the <a href="http://freedb.freedb.org/software/old/DBFORMAT">database-format specification</a>. DO NOT encode the data in any \r
+way before transmitting it; data must be sent as raw text. For example, \r
+Windows programmers should not use the Windows URL encode function prior to\r
+calling the submit CGI program. Doing so may lead to corrupt data being sent\r
+and also possibly to rejected submissions.\r
+\r
+You may implement a button or somesuch in your software's user interface\r
+to initiate submissions. Rejected submissions are automatically returned\r
+via email to the sender specified in the "User-Email" header field with an\r
+explanation of the reason for the rejection.\r
+\r
+Please do not allow a user to submit CD database entries that\r
+have completely unfilled contents (i.e., blank information in the\r
+disc artist/title as well as the track titles, or filled with\r
+useless default information like "track 1", "track 2", etc.).\r
+While the current CD database server checks and rejects submissions\r
+that have a blank DTITLE line, it doesn't (and can't feasibly) check\r
+the track titles effectively, nor can it check any of these fields\r
+if they are filled with a default string. If it were, it would\r
+have to be hacked to know about the default strings of every possible\r
+client.\r
+\r
+Thus, please design your client with this in mind. This is a somewhat\r
+tricky thing to do, as some CDs contain blank tracks with no titles\r
+and you need to allow for that. An example minimum requirement\r
+that a CD player client should meet is listed below:\r
+\r
+1. Don't allow the "send" or "submit" feature to be activated if\r
+ the CD database information form is not edited at all.\r
+2. Check that the disc artist/title contains something (that the user\r
+ typed in).\r
+3. Check that all of the tracks have a title filled in by the user.\r
+ (some (but not all!) may be blank, but not the default string).\r
+ \r
+Before you release your software, please be sure that it produces\r
+submissions that adhere to the CDDB file format, and that the frame\r
+offset, disc length, and disc ID information are correctly computed.\r
+For testing, please make your software send submissions with the\r
+"Submit-Mode" HTTP header field set to "test".\r
+\r
+<a name="testsubmission"></a>CDDB submissions sent in test mode will be sanity-checked by the freedb server\r
+and pass/fail confirmation sent back to the submitter, but will not actually\r
+be deposited in the CD database. Please DO NOT send submisions in "submit"\r
+mode until you have tested your program with several different CD's.\r
--- /dev/null
+#include "acd.h"
+
+Toc thetoc;
+
+void
+tocthread(void *v)
+{
+ Drive *d;
+
+ threadsetname("tocthread");
+ d = v;
+ DPRINT(2, "recv ctocdisp?...");
+ while(recv(d->ctocdisp, &thetoc) == 1) {
+ DPRINT(2, "recv ctocdisp!...");
+ drawtoc(d->w, &thetoc);
+ DPRINT(2, "send dbreq...\n");
+ send(d->ctocdbreq, &thetoc);
+ }
+}
+
+void
+freetoc(Toc *t)
+{
+ int i;
+
+ free(t->title);
+ for(i=0; i<t->ntrack; i++)
+ free(t->track[i].title);
+}
+
+void
+cddbthread(void *v)
+{
+ Drive *d;
+ Toc t;
+
+ threadsetname("cddbthread");
+ d = v;
+ while(recv(d->ctocdbreply, &t) == 1) {
+ if(thetoc.nchange == t.nchange) {
+ freetoc(&thetoc);
+ thetoc = t;
+ redrawtoc(d->w, &thetoc);
+ }
+ }
+}
+
+void
+cdstatusthread(void *v)
+{
+ Drive *d;
+ Cdstatus s;
+
+ d = v;
+
+ for(;;)
+ recv(d->cstat, &s);
+
+}
--- /dev/null
+#include "acd.h"
+
+void*
+emalloc(uint n)
+{
+ void *p;
+
+ p = malloc(n);
+ if(p == nil)
+ error("can't malloc: %r");
+ memset(p, 0, n);
+ return p;
+}
+
+char*
+estrdup(char *s)
+{
+ char *t;
+
+ t = emalloc(strlen(s)+1);
+ strcpy(t, s);
+ return t;
+}
+
+char*
+estrstrdup(char *s, char *t)
+{
+ char *u;
+
+ u = emalloc(strlen(s)+strlen(t)+1);
+ strcpy(u, s);
+ strcat(u, t);
+ return u;
+}
+
+char*
+eappend(char *s, char *sep, char *t)
+{
+ char *u;
+
+ if(t == nil)
+ u = estrstrdup(s, sep);
+ else{
+ u = emalloc(strlen(s)+strlen(sep)+strlen(t)+1);
+ strcpy(u, s);
+ strcat(u, sep);
+ strcat(u, t);
+ }
+ free(s);
+ return u;
+}
+
+char*
+egrow(char *s, char *sep, char *t)
+{
+ s = eappend(s, sep, t);
+ free(t);
+ return s;
+}
+
+void
+error(char *fmt, ...)
+{
+ int n;
+ va_list arg;
+ char buf[256];
+
+ fprint(2, "Mail: ");
+ va_start(arg, fmt);
+ n = vsnprint(buf, sizeof buf, fmt, arg);
+ va_end(arg);
+ write(2, buf, n);
+ write(2, "\n", 1);
+ threadexitsall(fmt);
+}
+
+void
+ctlprint(int fd, char *fmt, ...)
+{
+ int n;
+ va_list arg;
+ char buf[256];
+
+ va_start(arg, fmt);
+ n = vsnprint(buf, sizeof buf, fmt, arg);
+ va_end(arg);
+ if(write(fd, buf, n) != n)
+ error("control file write error: %r");
+}
--- /dev/null
+#include "acd.h"
+
+Window*
+newwindow(void)
+{
+ char buf[12];
+ Window *w;
+
+ w = emalloc(sizeof(Window));
+ w->ctl = open("/mnt/wsys/new/ctl", ORDWR|OCEXEC);
+ if(w->ctl<0 || read(w->ctl, buf, 12)!=12)
+ error("can't open window ctl file: %r");
+ ctlprint(w->ctl, "noscroll\n");
+ w->id = atoi(buf);
+ w->event = winopenfile(w, "event");
+ w->addr = -1; /* will be opened when needed */
+ w->body = nil;
+ w->data = -1;
+ w->cevent = chancreate(sizeof(Event*), 0);
+ if(w->cevent == nil)
+ error("cevent is nil: %r");
+ return w;
+}
+
+void
+winsetdump(Window *w, char *dir, char *cmd)
+{
+ if(dir != nil)
+ ctlprint(w->ctl, "dumpdir %s\n", dir);
+ if(cmd != nil)
+ ctlprint(w->ctl, "dump %s\n", cmd);
+}
+
+void
+wineventproc(void *v)
+{
+ Window *w;
+ int i;
+
+ threadsetname("wineventproc");
+ w = v;
+ for(i=0; ; i++){
+ if(i >= NEVENT)
+ i = 0;
+ wingetevent(w, &w->e[i]);
+ sendp(w->cevent, &w->e[i]);
+ }
+}
+
+int
+winopenfile(Window *w, char *f)
+{
+ char buf[64];
+ int fd;
+
+ sprint(buf, "/mnt/wsys/%d/%s", w->id, f);
+ fd = open(buf, ORDWR|OCEXEC);
+ if(fd < 0)
+ error("can't open window file %s: %r", f);
+ return fd;
+}
+
+void
+wintagwrite(Window *w, char *s, int n)
+{
+ int fd;
+
+ fd = winopenfile(w, "tag");
+ if(write(fd, s, n) != n)
+ error("tag write: %r");
+ close(fd);
+}
+
+void
+winname(Window *w, char *s)
+{
+ ctlprint(w->ctl, "name %s\n", s);
+}
+
+void
+winopenbody(Window *w, int mode)
+{
+ char buf[256];
+
+ sprint(buf, "/mnt/wsys/%d/body", w->id);
+ w->body = Bopen(buf, mode|OCEXEC);
+ if(w->body == nil)
+ error("can't open window body file: %r");
+}
+
+void
+winclosebody(Window *w)
+{
+ if(w->body != nil){
+ Bterm(w->body);
+ w->body = nil;
+ }
+}
+
+void
+winwritebody(Window *w, char *s, int n)
+{
+ if(w->body == nil)
+ winopenbody(w, OWRITE);
+ if(Bwrite(w->body, s, n) != n)
+ error("write error to window: %r");
+}
+
+int
+wingetec(Window *w)
+{
+ if(w->nbuf == 0){
+ w->nbuf = read(w->event, w->buf, sizeof w->buf);
+ if(w->nbuf <= 0){
+ /* probably because window has exited, and only called by wineventproc, so just shut down */
+ threadexits(nil);
+ }
+ w->bufp = w->buf;
+ }
+ w->nbuf--;
+ return *w->bufp++;
+}
+
+int
+wingeten(Window *w)
+{
+ int n, c;
+
+ n = 0;
+ while('0'<=(c=wingetec(w)) && c<='9')
+ n = n*10+(c-'0');
+ if(c != ' ')
+ error("event number syntax");
+ return n;
+}
+
+int
+wingeter(Window *w, char *buf, int *nb)
+{
+ Rune r;
+ int n;
+
+ r = wingetec(w);
+ buf[0] = r;
+ n = 1;
+ if(r >= Runeself) {
+ while(!fullrune(buf, n))
+ buf[n++] = wingetec(w);
+ chartorune(&r, buf);
+ }
+ *nb = n;
+ return r;
+}
+
+void
+wingetevent(Window *w, Event *e)
+{
+ int i, nb;
+
+ e->c1 = wingetec(w);
+ e->c2 = wingetec(w);
+ e->q0 = wingeten(w);
+ e->q1 = wingeten(w);
+ e->flag = wingeten(w);
+ e->nr = wingeten(w);
+ if(e->nr > EVENTSIZE)
+ error("event string too long");
+ e->nb = 0;
+ for(i=0; i<e->nr; i++){
+ e->r[i] = wingeter(w, e->b+e->nb, &nb);
+ e->nb += nb;
+ }
+ e->r[e->nr] = 0;
+ e->b[e->nb] = 0;
+ if(wingetec(w) != '\n')
+ error("event syntax error");
+}
+
+void
+winwriteevent(Window *w, Event *e)
+{
+ fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
+}
+
+static int
+nrunes(char *s, int nb)
+{
+ int i, n;
+ Rune r;
+
+ n = 0;
+ for(i=0; i<nb; n++)
+ i += chartorune(&r, s+i);
+ return n;
+}
+
+void
+winread(Window *w, uint q0, uint q1, char *data)
+{
+ int m, n, nr;
+ char buf[256];
+
+ if(w->addr < 0)
+ w->addr = winopenfile(w, "addr");
+ if(w->data < 0)
+ w->data = winopenfile(w, "data");
+ m = q0;
+ while(m < q1){
+ n = sprint(buf, "#%d", m);
+ if(write(w->addr, buf, n) != n)
+ error("error writing addr: %r");
+ n = read(w->data, buf, sizeof buf);
+ if(n <= 0)
+ error("reading data: %r");
+ nr = nrunes(buf, n);
+ while(m+nr >q1){
+ do; while(n>0 && (buf[--n]&0xC0)==0x80);
+ --nr;
+ }
+ if(n == 0)
+ break;
+ memmove(data, buf, n);
+ data += n;
+ *data = 0;
+ m += nr;
+ }
+}
+
+void
+windormant(Window *w)
+{
+ if(w->addr >= 0){
+ close(w->addr);
+ w->addr = -1;
+ }
+ if(w->body != nil){
+ Bterm(w->body);
+ w->body = nil;
+ }
+ if(w->data >= 0){
+ close(w->data);
+ w->data = -1;
+ }
+}
+
+
+int
+windel(Window *w, int sure)
+{
+ if(sure)
+ write(w->ctl, "delete\n", 7);
+ else if(write(w->ctl, "del\n", 4) != 4)
+ return 0;
+ /* event proc will die due to read error from event file */
+ windormant(w);
+ close(w->ctl);
+ w->ctl = -1;
+ close(w->event);
+ w->event = -1;
+ return 1;
+}
+
+void
+winclean(Window *w)
+{
+ if(w->body)
+ Bflush(w->body);
+ ctlprint(w->ctl, "clean\n");
+}
+
+int
+winsetaddr(Window *w, char *addr, int errok)
+{
+ if(w->addr < 0)
+ w->addr = winopenfile(w, "addr");
+ if(write(w->addr, addr, strlen(addr)) < 0){
+ if(!errok)
+ error("error writing addr(%s): %r", addr);
+ return 0;
+ }
+ return 1;
+}
+
+int
+winselect(Window *w, char *addr, int errok)
+{
+ if(winsetaddr(w, addr, errok)){
+ ctlprint(w->ctl, "dot=addr\n");
+ return 1;
+ }
+ return 0;
+}
+
+char*
+winreadbody(Window *w, int *np) /* can't use readfile because acme doesn't report the length */
+{
+ char *s;
+ int m, na, n;
+
+ if(w->body != nil)
+ winclosebody(w);
+ winopenbody(w, OREAD);
+ s = nil;
+ na = 0;
+ n = 0;
+ for(;;){
+ if(na < n+512){
+ na += 1024;
+ s = realloc(s, na+1);
+ }
+ m = Bread(w->body, s+n, na-n);
+ if(m <= 0)
+ break;
+ n += m;
+ }
+ s[n] = 0;
+ winclosebody(w);
+ *np = n;
+ return s;
+}
--- /dev/null
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include "win.h"
+#include "adict.h"
+
+char *prog = "adict";
+char *lprog = "/bin/adict";
+char *xprog = "/bin/dict";
+char *dict, *pattern, *curaddr[MAXMATCH], *curone, *args[6], buffer[80];
+char abuffer[80], fbuffer[80], pbuffer[80];
+int curindex, count, Eopen, Mopen;
+Win Mwin, Ewin, Dwin;
+
+void openwin(char*, char*, Win*, int);
+void handle(Win*, int);
+void rexec(void*);
+void pexec(void*);
+int getaddr(char*);
+
+void
+usage(void)
+{
+ threadprint(2, "usage: %s [-d dictname] [pattern]\n", argv0);
+ threadexitsall(nil);
+}
+
+void
+threadmain(int argc, char** argv)
+{
+ ARGBEGIN{
+ case 'd':
+ dict = strdup(ARGF());
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ /* if running as other name, note that fact */
+ if(access(argv0, AEXIST) == 0)
+ lprog = argv0;
+
+ switch(argc){
+ case 1:
+ pattern = pbuffer;
+ strcpy(pattern,argv[0]);
+ if(dict == nil)
+ dict = "oed";
+ break;
+ case 0:
+ break;
+ default:
+ usage();
+ }
+
+ if ((dict == nil) && (pattern == nil))
+ openwin(prog,"", &Dwin, Dictwin);
+ if (pattern == nil)
+ openwin(prog,"",&Ewin, Entrywin);
+ if ((count = getaddr(pattern)) <= 1)
+ openwin(prog,"Prev Next", &Ewin, Entrywin);
+ else
+ openwin(prog, "", &Mwin, Matchwin);
+}
+
+static int
+procrexec(char *xprog, ...)
+{
+ int fpipe[2];
+ void *rexarg[4];
+ Channel *c;
+ va_list va;
+ int i;
+ char *p;
+
+ pipe(fpipe);
+ va_start(va, xprog);
+ p = xprog;
+ for(i=0; p && i+1<nelem(args); i++){
+ args[i] = p;
+ p = va_arg(va, char*);
+ }
+ args[i] = nil;
+
+ c = chancreate(sizeof(ulong), 0);
+ rexarg[0] = xprog;
+ rexarg[1] = args;
+ rexarg[2] = fpipe;
+ rexarg[3] = c;
+
+ proccreate(rexec, rexarg, 8192);
+ recvul(c);
+ chanfree(c);
+ close(fpipe[1]);
+ return fpipe[0];
+}
+
+int
+getaddr(char *pattern)
+{
+ /* Get char offset into dictionary of matches. */
+
+ int fd, i;
+ Biobuf inbuf;
+ char *bufptr;
+char *obuf;
+
+ if (pattern == nil) {
+ curone = nil;
+ curindex = 0;
+ curaddr[curindex] = nil;
+ return 0;
+ }
+
+ sprint(buffer,"/%s/A", pattern);
+ fd = procrexec(xprog, "-d", dict, "-c", buffer, nil);
+ Binit(&inbuf, fd, OREAD);
+ i = 0;
+ curindex = 0;
+ while ((bufptr = Brdline(&inbuf, '\n')) != nil && (i < (MAXMATCH-1))) {
+ bufptr[Blinelen(&inbuf)-1] = 0;
+obuf=bufptr;
+ while (bufptr[0] != '#' && bufptr[0] != 0) bufptr++;
+if(bufptr[0] == 0)
+ print("whoops buf «%s»\n", obuf);
+ curaddr[i] = malloc(strlen(bufptr));
+ strcpy(curaddr[i], bufptr);
+ i++;
+ }
+ curaddr[i] = nil;
+ if (i == MAXMATCH)
+ threadprint(2, "Too many matches!\n");
+ Bterm(&inbuf);
+ close(fd);
+
+ curone = curaddr[curindex];
+ return(i);
+}
+
+char*
+getpattern(char *addr)
+{
+ /* Get the pattern corresponding to an absolute address.*/
+ int fd;
+ char *res, *t;
+
+ res = nil;
+ sprint(buffer,"%sh", addr);
+ fd = procrexec(xprog, "-d", dict, "-c", buffer, nil);
+ if (read(fd, pbuffer, 80) > 80)
+ threadprint(2, "Error in getting addres from dict.\n");
+ else {
+ t = pbuffer;
+ /* remove trailing whitespace, newline */
+ if (t != nil){
+ while(*t != 0 && *t != '\n')
+ t++;
+ if(t == 0 && t > pbuffer)
+ t--;
+ while(t >= pbuffer && (*t==' ' || *t=='\n' || *t=='\t' || *t=='\r'))
+ *t-- = 0;
+ }
+ res = pbuffer;
+ }
+ close(fd);
+ return(res);
+}
+
+char*
+chgaddr(int dir)
+{
+ /* Increment or decrement the current address (curone). */
+
+ int fd;
+ char *res, *t;
+
+ res = nil;
+ if (dir < 0)
+ sprint(buffer,"%s-a", curone);
+ else
+ sprint(buffer,"%s+a", curone);
+ fd = procrexec(xprog, "-d", dict, "-c", buffer, nil);
+ if (read(fd, abuffer, 80) > 80)
+ threadprint(2, "Error in getting addres from dict.\n");
+ else {
+ res = abuffer;
+ while (*res != '#') res++;
+ t = res;
+ while ((*t != '\n') && (t != nil)) t++;
+ if (t != nil) *t = 0;
+ }
+ close(fd);
+ return(res);
+}
+
+void
+dispdicts(Win *cwin)
+{
+ /* Display available dictionaries in window. */
+
+ int fd, nb, i;
+ char buf[1024], *t;
+
+ fd = procrexec(xprog, "-d", "?", nil);
+ wreplace(cwin, "0,$","",0); /* Clear window */
+ while ((nb = read(fd, buf, 1024)) > 0) {
+ t = buf;
+ i = 0;
+ if (strncmp("Usage", buf, 5) == 0) { /* Remove first line. */
+ while (t[0] != '\n') {
+ t++;
+ i++;
+ }
+ t++;
+ i++;
+ }
+ wwritebody(cwin, t, nb-i);
+ }
+ close(fd);
+ wclean(cwin);
+}
+
+void
+dispentry(Win *cwin)
+{
+ /* Display the current selection in window. */
+
+ int fd, nb;
+ char buf[BUFSIZE];
+
+ if (curone == nil) {
+ if (pattern != nil) {
+ sprint(buf,"Pattern not found.\n");
+ wwritebody(cwin, buf, 19);
+ wclean(cwin);
+ }
+ return;
+ }
+ sprint(buffer,"%sp", curone);
+ fd = procrexec(xprog, "-d", dict, "-c", buffer, nil);
+ wreplace(cwin, "0,$","",0); /* Clear window */
+ while ((nb = read(fd, buf, BUFSIZE)) > 0) {
+ wwritebody(cwin, buf, nb);
+ }
+ close(fd);
+ wclean(cwin);
+}
+
+void
+dispmatches(Win *cwin)
+{
+ /* Display the current matches. */
+
+ int fd, nb;
+ char buf[BUFSIZE];
+
+ sprint(buffer,"/%s/H", pattern);
+ fd = procrexec(xprog, "-d", dict, "-c", buffer, nil);
+ while ((nb = read(fd, buf, BUFSIZE)) > 0)
+ wwritebody(cwin, buf, nb);
+ close(fd);
+ wclean(cwin);
+}
+
+char*
+format(char *s)
+{
+ /* Format a string to be written in window tag. Acme doesn't like */
+ /* non alpha-num's in the tag line. */
+
+ char *t, *h;
+
+ t = fbuffer;
+ if (s == nil) {
+ *t = 0;
+ return t;
+ }
+ strcpy(t, s);
+ h = t;
+ while (*t != 0) {
+ if (!(((*t >= 'a') && (*t <= 'z')) ||
+ ((*t >= 'A') && (*t <= 'Z')) ||
+ ((*t >= '0') && (*t <= '9'))))
+ *t = '_';
+ t++;
+ }
+ if (strlen(h) > MAXTAG)
+ h[MAXTAG] = 0;
+ if (strcmp(s,h) == 0) return s;
+ return h;
+}
+
+void
+openwin(char *name, char *buttons, Win *twin, int wintype)
+{
+ char buf[80];
+
+ wnew(twin);
+ if (wintype == Dictwin)
+ sprint(buf,"%s",name);
+ else
+ if ((wintype == Entrywin) && (count > 1))
+ sprint(buf,"%s/%s/%s/%d",name, dict, format(pattern), curindex+1);
+ else
+ sprint(buf,"%s/%s/%s",name, dict, format(pattern));
+ wname(twin, buf);
+ wtagwrite(twin, buttons, strlen(buttons));
+ wclean(twin);
+ wdormant(twin);
+ if (wintype == Dictwin)
+ dispdicts(twin);
+ if (wintype == Matchwin) {
+ Mopen = True;
+ dispmatches(twin);
+ }
+ if (wintype == Entrywin) {
+ Eopen = True;
+ dispentry(twin);
+ }
+ handle(twin, wintype);
+}
+
+void
+vopenwin(void *v)
+{
+ void **arg;
+ char *name, *buttons;
+ Win *twin;
+ int wintype;
+
+ arg = v;
+ name = arg[0];
+ buttons = arg[1];
+ twin = arg[2];
+ wintype = (int)arg[3];
+ sendul(arg[4], 0);
+
+ openwin(name, buttons, twin, wintype);
+ threadexits(nil);
+}
+
+void
+procopenwin(char *name, char *buttons, Win *twin, int wintype)
+{
+ void *arg[5];
+ Channel *c;
+
+ c = chancreate(sizeof(ulong), 0);
+ arg[0] = name;
+ arg[1] = buttons;
+ arg[2] = twin;
+ arg[3] = (void*)wintype;
+ arg[4] = c;
+ proccreate(vopenwin, arg, 8192);
+ recvul(c);
+ chanfree(c);
+}
+
+void
+rexec(void *v)
+{
+ void **arg;
+ char *prog;
+ char **args;
+ int *fd;
+ Channel *c;
+
+ arg = v;
+ prog = arg[0];
+ args = arg[1];
+ fd = arg[2];
+ c = arg[3];
+
+ rfork(RFENVG|RFFDG);
+ dup(fd[1], 1);
+ close(fd[1]);
+ close(fd[0]);
+ procexec(c, prog, args);
+ threadprint(2, "Remote pipe execution failed: %s %r\n", prog);
+abort();
+ threadexits(nil);
+}
+
+void
+pexec(void *v)
+{
+ void **arg;
+ char *prog;
+ char **args;
+ Channel *c;
+
+ arg = v;
+ prog = arg[0];
+ args = arg[1];
+ c = arg[2];
+
+ procexec(c, prog, args);
+ threadprint(2, "Remote execution failed: %s %r\n", prog);
+abort();
+ threadexits(nil);
+}
+
+void
+procpexec(char *prog, char **args)
+{
+ void *rexarg[4];
+ Channel *c;
+
+ c = chancreate(sizeof(ulong), 0);
+ rexarg[0] = prog;
+ rexarg[1] = args;
+ rexarg[2] = c;
+
+ proccreate(pexec, rexarg, 8192);
+ recvul(c);
+ chanfree(c);
+}
+
+void
+kill(void)
+{
+ /* Kill all processes related to this one. */
+ int fd;
+
+ sprint(buffer, "/proc/%d/notepg", getpid());
+ fd = open(buffer, OWRITE);
+ rfork(RFNOTEG);
+ write(fd, "kill", 4);
+}
+
+int
+command(char *com, Win *w, int wintype)
+{
+ char *buf;
+
+ if (strncmp(com, "Del", 3) == 0) {
+ switch(wintype){
+ case Entrywin:
+ if (wdel(w)) {
+ Eopen = False;
+ threadexits(nil);
+ }
+ break;
+ case Dictwin:
+ if (wdel(w))
+ threadexits(nil);
+ break;
+ case Matchwin:
+ kill();
+ if (Eopen)
+ if (~wdel(&Ewin)) /* Remove the entry window */
+ wdel(&Ewin);
+ if (!wdel(w))
+ wdel(w);
+ threadexits(nil);
+ break;
+ }
+ return True;
+ }
+ if (strncmp(com, "Next", 4) == 0){
+ if (curone != nil) {
+ curone = chgaddr(1);
+ buf = getpattern(curone);
+ sprint(buffer,"%s/%s/%s", prog, dict, format(buf));
+ wname(w, buffer);
+ dispentry(w);
+ }
+ return True;
+ }
+ if (strncmp(com, "Prev",4) == 0){
+ if (curone != nil) {
+ curone = chgaddr(-1);
+ buf = getpattern(curone);
+ sprint(buffer,"%s/%s/%s", prog, dict, format(buf));
+ wname(w, buffer);
+ dispentry(w);
+ }
+ return True;
+ }
+ if (strncmp(com, "Nmatch",6) == 0){
+ if (curaddr[++curindex] == nil)
+ curindex = 0;
+ curone = curaddr[curindex];
+ if (curone != nil) {
+ sprint(buffer,"%s/%s/%s/%d",prog,dict,format(pattern),curindex+1);
+ wname(w, buffer);
+ dispentry(w);
+ }
+ return True;
+ }
+ return False;
+}
+
+void
+handle(Win *w, int wintype)
+{
+ Event e, e2, ea, etoss;
+ char *s, *t, buf[80];
+ int tmp, na;
+
+ while (True) {
+ wevent(w, &e);
+ switch(e.c2){
+ default:
+ /* threadprint(2,"unknown message %c%c\n", e.c1, e.c2); */
+ break;
+ case 'i':
+ /* threadprint(2,"'%s' inserted in tag at %d\n", e.b, e.q0);*/
+ break;
+ case 'I':
+ /* threadprint(2,"'%s' inserted in body at %d\n", e.b, e.q0);*/
+ break;
+ case 'd':
+ /* threadprint(2, "'%s' deleted in tag at %d\n", e.b, e.q0);*/
+ break;
+ case 'D':
+ /* threadprint(2, "'%s' deleted in body at %d\n", e.b, e.q0);*/
+ break;
+ case 'x':
+ case 'X': /* Execute command. */
+ if (e.flag & 2)
+ wevent(w, &e2);
+ if(e.flag & 8){
+ wevent(w, &ea);
+ wevent(w, &etoss);
+ na = ea.nb;
+ } else
+ na = 0;
+ s = e.b;
+ if ((e.flag & 2) && e.nb == 0)
+ s = e2.b;
+ if(na){
+ t = malloc(strlen(s)+1+na+1);
+ snprint(t, strlen(s)+1+na+1, "%s %s", s, ea.b);
+ s = t;
+ }
+ /* if it's a long message, it can't be for us anyway */
+ if(!command(s, w, wintype)) /* send it back */
+ wwriteevent(w, &e);
+ if(na)
+ free(s);
+ break;
+ case 'l':
+ case 'L': /* Look for something. */
+ if (e.flag & 2)
+ wevent(w, &e);
+ wclean(w); /* Set clean bit. */
+ if (wintype == Dictwin) {
+ strcpy(buf, e.b);
+ args[0] = lprog;
+ args[1] = "-d";
+ args[2] = buf;
+ args[3] = nil;
+ procpexec(lprog, args); /* New adict with chosen dict. */
+ }
+ if (wintype == Entrywin) {
+ strcpy(buf, e.b);
+ args[0] = lprog;
+ args[1] = "-d";
+ args[2] = dict;
+ args[3] = buf;
+ args[4] = nil;
+ procpexec(lprog, args); /* New adict with chosen pattern. */
+ }
+ if (wintype == Matchwin) {
+ tmp = atoi(e.b) - 1;
+ if ((tmp >= 0) && (tmp < MAXMATCH) && (curaddr[tmp] != nil)) {
+ curindex = tmp;
+ curone = curaddr[curindex];
+ /* Display selected match. */
+ if (Eopen) {
+ sprint(buf,"%s/%s/%s/%d",prog,dict,format(pattern),curindex+1);
+ wname(&Ewin, buf);
+ dispentry(&Ewin);
+ }
+ else
+ procopenwin(prog,"Nmatch Prev Next", &Ewin, Entrywin);
+ }
+ }
+ break;
+ }
+ }
+}
--- /dev/null
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include "win.h"
+
+void*
+erealloc(void *p, uint n)
+{
+ p = realloc(p, n);
+ if(p == nil)
+ threadprint(2, "realloc failed: %r");
+ return p;
+}
+
+void
+wnew(Win *w)
+{
+ char buf[12];
+
+ w->ctl = open("/mnt/acme/new/ctl", ORDWR);
+ if(w->ctl<0 || read(w->ctl, buf, 12)!=12)
+ threadprint (2, "can't open window ctl file: %r");
+ ctlwrite(w, "noscroll\n");
+ w->winid = atoi(buf);
+ w->event = openfile(w, "event");
+ w->addr = -1; /* will be opened when needed */
+ w->body = nil;
+ w->data = -1;
+}
+
+int
+openfile(Win *w, char *f)
+{
+ char buf[64];
+ int fd;
+
+ sprint(buf, "/mnt/acme/%d/%s", w->winid, f);
+ fd = open(buf, ORDWR|OCEXEC);
+ if(fd < 0)
+ threadprint (2,"can't open window %s file: %r", f);
+ return fd;
+}
+
+void
+openbody(Win *w, int mode)
+{
+ char buf[64];
+
+ sprint(buf, "/mnt/acme/%d/body", w->winid);
+ w->body = Bopen(buf, mode|OCEXEC);
+ if(w->body == nil)
+ threadprint(2,"can't open window body file: %r");
+}
+
+void
+wwritebody(Win *w, char *s, int n)
+{
+ if(w->body == nil)
+ openbody(w, OWRITE);
+ if(Bwrite(w->body, s, n) != n)
+ threadprint(2,"write error to window: %r");
+ Bflush(w->body);
+}
+
+void
+wreplace(Win *w, char *addr, char *repl, int nrepl)
+{
+ if(w->addr < 0)
+ w->addr = openfile(w, "addr");
+ if(w->data < 0)
+ w->data = openfile(w, "data");
+ if(write(w->addr, addr, strlen(addr)) < 0){
+ threadprint(2, "mail: warning: badd address %s:%r\n", addr);
+ return;
+ }
+ if(write(w->data, repl, nrepl) != nrepl)
+ threadprint(2, "writing data: %r");
+}
+
+static int
+nrunes(char *s, int nb)
+{
+ int i, n;
+ Rune r;
+
+ n = 0;
+ for(i=0; i<nb; n++)
+ i += chartorune(&r, s+i);
+ return n;
+}
+
+void
+wread(Win *w, uint q0, uint q1, char *data)
+{
+ int m, n, nr;
+ char buf[256];
+
+ if(w->addr < 0)
+ w->addr = openfile(w, "addr");
+ if(w->data < 0)
+ w->data = openfile(w, "data");
+ m = q0;
+ while(m < q1){
+ n = sprint(buf, "#%d", m);
+ if(write(w->addr, buf, n) != n)
+ threadprint(2,"writing addr: %r");
+ n = read(w->data, buf, sizeof buf);
+ if(n <= 0)
+ threadprint(2,"reading data: %r");
+ nr = nrunes(buf, n);
+ while(m+nr >q1){
+ do; while(n>0 && (buf[--n]&0xC0)==0x80);
+ --nr;
+ }
+ if(n == 0)
+ break;
+ memmove(data, buf, n);
+ data += n;
+ *data = 0;
+ m += nr;
+ }
+}
+
+void
+wselect(Win *w, char *addr)
+{
+ if(w->addr < 0)
+ w->addr = openfile(w, "addr");
+ if(write(w->addr, addr, strlen(addr)) < 0)
+ threadprint(2,"writing addr");
+ ctlwrite(w, "dot=addr\n");
+}
+
+void
+wtagwrite(Win *w, char *s, int n)
+{
+ int fd;
+
+ fd = openfile(w, "tag");
+ if(write(fd, s, n) != n)
+ threadprint(2,"tag write: %r");
+ close(fd);
+}
+
+void
+ctlwrite(Win *w, char *s)
+{
+ int n;
+
+ n = strlen(s);
+ if(write(w->ctl, s, n) != n)
+ threadprint(2,"write error to ctl file: %r");
+}
+
+int
+wdel(Win *w)
+{
+ if(write(w->ctl, "del\n", 4) != 4)
+ return False;
+ wdormant(w);
+ close(w->ctl);
+ w->ctl = -1;
+ close(w->event);
+ w->event = -1;
+ return True;
+}
+
+void
+wname(Win *w, char *s)
+{
+ char buf[128];
+
+ sprint(buf, "name %s\n", s);
+ ctlwrite(w, buf);
+}
+
+void
+wclean(Win *w)
+{
+ if(w->body)
+ Bflush(w->body);
+ ctlwrite(w, "clean\n");
+}
+
+void
+wdormant(Win *w)
+{
+ if(w->addr >= 0){
+ close(w->addr);
+ w->addr = -1;
+ }
+ if(w->body != nil){
+ Bterm(w->body);
+ w->body = nil;
+ }
+ if(w->data >= 0){
+ close(w->data);
+ w->data = -1;
+ }
+}
+
+int
+getec(Win *w)
+{
+ if(w->nbuf == 0){
+ w->nbuf = read(w->event, w->buf, sizeof w->buf);
+ if(w->nbuf <= 0)
+ threadprint(2,"event read error: %r");
+ w->bufp = w->buf;
+ }
+ w->nbuf--;
+ return *w->bufp++;
+}
+
+int
+geten(Win *w)
+{
+ int n, c;
+
+ n = 0;
+ while('0'<=(c=getec(w)) && c<='9')
+ n = n*10+(c-'0');
+ if(c != ' ')
+ threadprint(2, "event number syntax");
+ return n;
+}
+
+int
+geter(Win *w, char *buf, int *nb)
+{
+ Rune r;
+ int n;
+
+ r = getec(w);
+ buf[0] = r;
+ n = 1;
+ if(r < Runeself)
+ goto Return;
+ while(!fullrune(buf, n))
+ buf[n++] = getec(w);
+ chartorune(&r, buf);
+ Return:
+ *nb = n;
+ return r;
+}
+
+
+void
+wevent(Win *w, Event *e)
+{
+ int i, nb;
+
+ e->c1 = getec(w);
+ e->c2 = getec(w);
+ e->q0 = geten(w);
+ e->q1 = geten(w);
+ e->flag = geten(w);
+ e->nr = geten(w);
+ if(e->nr > EVENTSIZE)
+ threadprint(2, "wevent: event string too long");
+ e->nb = 0;
+ for(i=0; i<e->nr; i++){
+ e->r[i] = geter(w, e->b+e->nb, &nb);
+ e->nb += nb;
+ }
+ e->r[e->nr] = 0;
+ e->b[e->nb] = 0;
+ if(getec(w) != '\n')
+ threadprint(2, "wevent: event syntax 2");
+}
+
+void
+wslave(Win *w, Channel *ce)
+{
+ Event e;
+
+ while(recv(ce, &e) >= 0)
+ wevent(w, &e);
+}
+
+void
+wwriteevent(Win *w, Event *e)
+{
+ threadprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
+}
+
+int
+wreadall(Win *w, char **sp)
+{
+ char *s;
+ int m, na, n;
+
+ if(w->body != nil)
+ Bterm(w->body);
+ openbody(w, OREAD);
+ s = nil;
+ na = 0;
+ n = 0;
+ for(;;){
+ if(na < n+512){
+ na += 1024;
+ s = erealloc(s, na+1);
+ }
+ m = Bread(w->body, s+n, na-n);
+ if(m <= 0)
+ break;
+ n += m;
+ }
+ s[n] = 0;
+ Bterm(w->body);
+ w->body = nil;
+ *sp = s;
+ return n;
+}
--- /dev/null
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include "win.h"
+#include "adict.h"
+
+enum
+{
+ STACK = 8192,
+};
+
+char *prog = "adict";
+char *lprog = "/bin/adict";
+char *xprog = "/bin/dict";
+char *dict, *pattern, *curaddr[MAXMATCH], *curone, *args[6], buffer[80];
+char abuffer[80], fbuffer[80], pbuffer[80];
+int curindex, count, Eopen, Mopen;
+Win Mwin, Ewin, Dwin;
+
+void openwin(char*, char*, Win*, int);
+void handle(Win*, int);
+void rexec(void*);
+void pexec(void*);
+int getaddr(char*);
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-d dictname] [pattern]\n", argv0);
+ threadexitsall(nil);
+}
+
+int mainstacksize = STACK;
+
+void
+threadmain(int argc, char** argv)
+{
+ ARGBEGIN{
+ case 'd':
+ dict = strdup(ARGF());
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ /* if running as other name, note that fact */
+ if(access(argv0, AEXIST) == 0)
+ lprog = argv0;
+
+ switch(argc){
+ case 1:
+ pattern = pbuffer;
+ strcpy(pattern,argv[0]);
+ if(dict == nil)
+ dict = "pgw";
+ break;
+ case 0:
+ break;
+ default:
+ usage();
+ }
+
+ if ((dict == nil) && (pattern == nil))
+ openwin(prog,"", &Dwin, Dictwin);
+ if (pattern == nil)
+ openwin(prog,"",&Ewin, Entrywin);
+ if ((count = getaddr(pattern)) <= 1)
+ openwin(prog,"Prev Next", &Ewin, Entrywin);
+ else
+ openwin(prog, "", &Mwin, Matchwin);
+}
+
+static int
+procrexec(char *xprog, ...)
+{
+ int fpipe[2];
+ void *rexarg[4];
+ Channel *c;
+ va_list va;
+ int i;
+ char *p;
+
+ pipe(fpipe);
+ va_start(va, xprog);
+ p = xprog;
+ for(i=0; p && i+1<nelem(args); i++){
+ args[i] = p;
+ p = va_arg(va, char*);
+ }
+ args[i] = nil;
+
+ c = chancreate(sizeof(ulong), 0);
+ rexarg[0] = xprog;
+ rexarg[1] = args;
+ rexarg[2] = fpipe;
+ rexarg[3] = c;
+
+ proccreate(rexec, rexarg, STACK);
+ recvul(c);
+ chanfree(c);
+ close(fpipe[1]);
+ return fpipe[0];
+}
+
+int
+getaddr(char *pattern)
+{
+ /* Get char offset into dictionary of matches. */
+
+ int fd, i;
+ Biobuf inbuf;
+ char *bufptr;
+char *obuf;
+
+ if (pattern == nil) {
+ curone = nil;
+ curindex = 0;
+ curaddr[curindex] = nil;
+ return 0;
+ }
+
+ sprint(buffer,"/%s/A", pattern);
+ fd = procrexec(xprog, "-d", dict, "-c", buffer, nil);
+ Binit(&inbuf, fd, OREAD);
+ i = 0;
+ curindex = 0;
+ while ((bufptr = Brdline(&inbuf, '\n')) != nil && (i < (MAXMATCH-1))) {
+ bufptr[Blinelen(&inbuf)-1] = 0;
+obuf=bufptr;
+ while (bufptr[0] != '#' && bufptr[0] != 0) bufptr++;
+if(bufptr[0] == 0)
+ print("whoops buf «%s»\n", obuf);
+ curaddr[i] = malloc(strlen(bufptr));
+ strcpy(curaddr[i], bufptr);
+ i++;
+ }
+ curaddr[i] = nil;
+ if (i == MAXMATCH)
+ fprint(2, "Too many matches!\n");
+ Bterm(&inbuf);
+ close(fd);
+
+ curone = curaddr[curindex];
+ return(i);
+}
+
+char*
+getpattern(char *addr)
+{
+ /* Get the pattern corresponding to an absolute address.*/
+ int fd;
+ char *res, *t;
+
+ res = nil;
+ sprint(buffer,"%sh", addr);
+ fd = procrexec(xprog, "-d", dict, "-c", buffer, nil);
+ if (read(fd, pbuffer, 80) > 80)
+ fprint(2, "Error in getting addres from dict.\n");
+ else {
+ t = pbuffer;
+ /* remove trailing whitespace, newline */
+ if (t != nil){
+ while(*t != 0 && *t != '\n')
+ t++;
+ if(t == 0 && t > pbuffer)
+ t--;
+ while(t >= pbuffer && (*t==' ' || *t=='\n' || *t=='\t' || *t=='\r'))
+ *t-- = 0;
+ }
+ res = pbuffer;
+ }
+ close(fd);
+ return(res);
+}
+
+char*
+chgaddr(int dir)
+{
+ /* Increment or decrement the current address (curone). */
+
+ int fd;
+ char *res, *t;
+
+ res = nil;
+ if (dir < 0)
+ sprint(buffer,"%s-a", curone);
+ else
+ sprint(buffer,"%s+a", curone);
+ fd = procrexec(xprog, "-d", dict, "-c", buffer, nil);
+ if (read(fd, abuffer, 80) > 80)
+ fprint(2, "Error in getting addres from dict.\n");
+ else {
+ res = abuffer;
+ while (*res != '#') res++;
+ t = res;
+ while ((*t != '\n') && (t != nil)) t++;
+ if (t != nil) *t = 0;
+ }
+ close(fd);
+ return(res);
+}
+
+void
+dispdicts(Win *cwin)
+{
+ /* Display available dictionaries in window. */
+
+ int fd, nb, i;
+ char buf[1024], *t;
+
+ fd = procrexec(xprog, "-d", "?", nil);
+ wreplace(cwin, "0,$","",0); /* Clear window */
+ while ((nb = read(fd, buf, 1024)) > 0) {
+ t = buf;
+ i = 0;
+ if (strncmp("Usage", buf, 5) == 0) { /* Remove first line. */
+ while (t[0] != '\n') {
+ t++;
+ i++;
+ }
+ t++;
+ i++;
+ }
+ wwritebody(cwin, t, nb-i);
+ }
+ close(fd);
+ wclean(cwin);
+}
+
+void
+dispentry(Win *cwin)
+{
+ /* Display the current selection in window. */
+
+ int fd, nb;
+ char buf[BUFSIZE];
+
+ if (curone == nil) {
+ if (pattern != nil) {
+ sprint(buf,"Pattern not found.\n");
+ wwritebody(cwin, buf, 19);
+ wclean(cwin);
+ }
+ return;
+ }
+ sprint(buffer,"%sp", curone);
+ fd = procrexec(xprog, "-d", dict, "-c", buffer, nil);
+ wreplace(cwin, "0,$","",0); /* Clear window */
+ while ((nb = read(fd, buf, BUFSIZE)) > 0) {
+ wwritebody(cwin, buf, nb);
+ }
+ close(fd);
+ wclean(cwin);
+}
+
+void
+dispmatches(Win *cwin)
+{
+ /* Display the current matches. */
+
+ int fd, nb;
+ char buf[BUFSIZE];
+
+ sprint(buffer,"/%s/H", pattern);
+ fd = procrexec(xprog, "-d", dict, "-c", buffer, nil);
+ while ((nb = read(fd, buf, BUFSIZE)) > 0)
+ wwritebody(cwin, buf, nb);
+ close(fd);
+ wclean(cwin);
+}
+
+char*
+format(char *s)
+{
+ /* Format a string to be written in window tag. Acme doesn't like */
+ /* non alpha-num's in the tag line. */
+
+ char *t, *h;
+
+ t = fbuffer;
+ if (s == nil) {
+ *t = 0;
+ return t;
+ }
+ strcpy(t, s);
+ h = t;
+ while (*t != 0) {
+ if (!(((*t >= 'a') && (*t <= 'z')) ||
+ ((*t >= 'A') && (*t <= 'Z')) ||
+ ((*t >= '0') && (*t <= '9'))))
+ *t = '_';
+ t++;
+ }
+ if (strlen(h) > MAXTAG)
+ h[MAXTAG] = 0;
+ if (strcmp(s,h) == 0) return s;
+ return h;
+}
+
+void
+openwin(char *name, char *buttons, Win *twin, int wintype)
+{
+ char buf[80];
+
+ wnew(twin);
+ if (wintype == Dictwin)
+ sprint(buf,"%s",name);
+ else
+ if ((wintype == Entrywin) && (count > 1))
+ sprint(buf,"%s/%s/%s/%d",name, dict, format(pattern), curindex+1);
+ else
+ sprint(buf,"%s/%s/%s",name, dict, format(pattern));
+ wname(twin, buf);
+ wtagwrite(twin, buttons, strlen(buttons));
+ wclean(twin);
+ wdormant(twin);
+ if (wintype == Dictwin)
+ dispdicts(twin);
+ if (wintype == Matchwin) {
+ Mopen = True;
+ dispmatches(twin);
+ }
+ if (wintype == Entrywin) {
+ Eopen = True;
+ dispentry(twin);
+ }
+ handle(twin, wintype);
+}
+
+void
+vopenwin(void *v)
+{
+ void **arg;
+ char *name, *buttons;
+ Win *twin;
+ int wintype;
+
+ arg = v;
+ name = arg[0];
+ buttons = arg[1];
+ twin = arg[2];
+ wintype = (int)arg[3];
+ sendul(arg[4], 0);
+
+ openwin(name, buttons, twin, wintype);
+ threadexits(nil);
+}
+
+void
+procopenwin(char *name, char *buttons, Win *twin, int wintype)
+{
+ void *arg[5];
+ Channel *c;
+
+ c = chancreate(sizeof(ulong), 0);
+ arg[0] = name;
+ arg[1] = buttons;
+ arg[2] = twin;
+ arg[3] = (void*)wintype;
+ arg[4] = c;
+ proccreate(vopenwin, arg, STACK);
+ recvul(c);
+ chanfree(c);
+}
+
+void
+rexec(void *v)
+{
+ void **arg;
+ char *prog;
+ char **args;
+ int *fd;
+ Channel *c;
+
+ arg = v;
+ prog = arg[0];
+ args = arg[1];
+ fd = arg[2];
+ c = arg[3];
+
+ rfork(RFENVG|RFFDG);
+ dup(fd[1], 1);
+ close(fd[1]);
+ close(fd[0]);
+ procexec(c, prog, args);
+ fprint(2, "Remote pipe execution failed: %s %r\n", prog);
+abort();
+ threadexits(nil);
+}
+
+void
+pexec(void *v)
+{
+ void **arg;
+ char *prog;
+ char **args;
+ Channel *c;
+
+ arg = v;
+ prog = arg[0];
+ args = arg[1];
+ c = arg[2];
+
+ procexec(c, prog, args);
+ fprint(2, "Remote execution failed: %s %r\n", prog);
+abort();
+ threadexits(nil);
+}
+
+void
+procpexec(char *prog, char **args)
+{
+ void *rexarg[4];
+ Channel *c;
+
+ c = chancreate(sizeof(ulong), 0);
+ rexarg[0] = prog;
+ rexarg[1] = args;
+ rexarg[2] = c;
+
+ proccreate(pexec, rexarg, STACK);
+ recvul(c);
+ chanfree(c);
+}
+
+void
+kill(void)
+{
+ /* Kill all processes related to this one. */
+ int fd;
+
+ sprint(buffer, "/proc/%d/notepg", getpid());
+ fd = open(buffer, OWRITE);
+ rfork(RFNOTEG);
+ write(fd, "kill", 4);
+}
+
+int
+command(char *com, Win *w, int wintype)
+{
+ char *buf;
+
+ if (strncmp(com, "Del", 3) == 0) {
+ switch(wintype){
+ case Entrywin:
+ if (wdel(w)) {
+ Eopen = False;
+ threadexits(nil);
+ }
+ break;
+ case Dictwin:
+ if (wdel(w))
+ threadexits(nil);
+ break;
+ case Matchwin:
+ kill();
+ if (Eopen)
+ if (~wdel(&Ewin)) /* Remove the entry window */
+ wdel(&Ewin);
+ if (!wdel(w))
+ wdel(w);
+ threadexits(nil);
+ break;
+ }
+ return True;
+ }
+ if (strncmp(com, "Next", 4) == 0){
+ if (curone != nil) {
+ curone = chgaddr(1);
+ buf = getpattern(curone);
+ sprint(buffer,"%s/%s/%s", prog, dict, format(buf));
+ wname(w, buffer);
+ dispentry(w);
+ }
+ return True;
+ }
+ if (strncmp(com, "Prev",4) == 0){
+ if (curone != nil) {
+ curone = chgaddr(-1);
+ buf = getpattern(curone);
+ sprint(buffer,"%s/%s/%s", prog, dict, format(buf));
+ wname(w, buffer);
+ dispentry(w);
+ }
+ return True;
+ }
+ if (strncmp(com, "Nmatch",6) == 0){
+ if (curaddr[++curindex] == nil)
+ curindex = 0;
+ curone = curaddr[curindex];
+ if (curone != nil) {
+ sprint(buffer,"%s/%s/%s/%d",prog,dict,format(pattern),curindex+1);
+ wname(w, buffer);
+ dispentry(w);
+ }
+ return True;
+ }
+ return False;
+}
+
+void
+handle(Win *w, int wintype)
+{
+ Event e, e2, ea, etoss;
+ char *s, *t, buf[80];
+ int tmp, na;
+
+ while (True) {
+ wevent(w, &e);
+ switch(e.c2){
+ default:
+ /* fprint(2,"unknown message %c%c\n", e.c1, e.c2); */
+ break;
+ case 'i':
+ /* fprint(2,"'%s' inserted in tag at %d\n", e.b, e.q0);*/
+ break;
+ case 'I':
+ /* fprint(2,"'%s' inserted in body at %d\n", e.b, e.q0);*/
+ break;
+ case 'd':
+ /* fprint(2, "'%s' deleted in tag at %d\n", e.b, e.q0);*/
+ break;
+ case 'D':
+ /* fprint(2, "'%s' deleted in body at %d\n", e.b, e.q0);*/
+ break;
+ case 'x':
+ case 'X': /* Execute command. */
+ if (e.flag & 2)
+ wevent(w, &e2);
+ if(e.flag & 8){
+ wevent(w, &ea);
+ wevent(w, &etoss);
+ na = ea.nb;
+ } else
+ na = 0;
+ s = e.b;
+ if ((e.flag & 2) && e.nb == 0)
+ s = e2.b;
+ if(na){
+ t = malloc(strlen(s)+1+na+1);
+ snprint(t, strlen(s)+1+na+1, "%s %s", s, ea.b);
+ s = t;
+ }
+ /* if it's a long message, it can't be for us anyway */
+ if(!command(s, w, wintype)) /* send it back */
+ wwriteevent(w, &e);
+ if(na)
+ free(s);
+ break;
+ case 'l':
+ case 'L': /* Look for something. */
+ if (e.flag & 2)
+ wevent(w, &e);
+ wclean(w); /* Set clean bit. */
+ if (wintype == Dictwin) {
+ strcpy(buf, e.b);
+ args[0] = lprog;
+ args[1] = "-d";
+ args[2] = buf;
+ args[3] = nil;
+ procpexec(lprog, args); /* New adict with chosen dict. */
+ }
+ if (wintype == Entrywin) {
+ strcpy(buf, e.b);
+ args[0] = lprog;
+ args[1] = "-d";
+ args[2] = dict;
+ args[3] = buf;
+ args[4] = nil;
+ procpexec(lprog, args); /* New adict with chosen pattern. */
+ }
+ if (wintype == Matchwin) {
+ tmp = atoi(e.b) - 1;
+ if ((tmp >= 0) && (tmp < MAXMATCH) && (curaddr[tmp] != nil)) {
+ curindex = tmp;
+ curone = curaddr[curindex];
+ /* Display selected match. */
+ if (Eopen) {
+ sprint(buf,"%s/%s/%s/%d",prog,dict,format(pattern),curindex+1);
+ wname(&Ewin, buf);
+ dispentry(&Ewin);
+ }
+ else
+ procopenwin(prog,"Nmatch Prev Next", &Ewin, Entrywin);
+ }
+ }
+ break;
+ }
+ }
+}
--- /dev/null
+enum
+{
+ Matchwin,
+ Entrywin,
+ Dictwin
+};
+
+#define MAXTAG 20
+#define MAXMATCH 100
+#define BUFSIZE 4096
--- /dev/null
+adict [-d dictionary] [pattern]
+
+ adict with no arguments opens a window that displays all the currently
+available dictionaries. To select a dictionary, click the right mouse button on
+its name.
+
+-d dictionary Opens a window that interfaces to the specified dictionary. To
+ look up a word, enter it in the window, and click the right mouse button on it.
+
+[pattern] If no dictionary is specified, adict looks up the pattern in "oed" (Oxford
+ English Dictionary). If more than one entry is found, adict opens a window
+ displaying the headers of the matching entries. To display a particular entry
+ click the right mouse button on the number to its left.
+
+Quit Exit and remove all windows associated with this one.
+
+Nmatch Display the next matching entry.
+
+Next Display the next entry in the dictionary.
+
+Prev Display the previous entry in the dictionary.
+
+ Nmatch works independently of Prev and Next.
+
+ Any word in the window displaying an entry can be looked up in the selected
+dictionary by clicking the right mouse button on that word.
\ No newline at end of file
--- /dev/null
+</$objtype/mkfile
+
+TARG=adict
+
+HFILES=win.h
+
+OFILES=adict.$O\
+ win.$O\
+
+BIN= /acme/bin/$objtype
+</sys/src/cmd/mkone
--- /dev/null
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include "win.h"
+
+void*
+erealloc(void *p, uint n)
+{
+ p = realloc(p, n);
+ if(p == nil)
+ fprint(2, "realloc failed: %r");
+ return p;
+}
+
+void
+wnew(Win *w)
+{
+ char buf[12];
+
+ w->ctl = open("/mnt/acme/new/ctl", ORDWR);
+ if(w->ctl<0 || read(w->ctl, buf, 12)!=12)
+ fprint (2, "can't open window ctl file: %r");
+ ctlwrite(w, "noscroll\n");
+ w->winid = atoi(buf);
+ w->event = openfile(w, "event");
+ w->addr = -1; /* will be opened when needed */
+ w->body = nil;
+ w->data = -1;
+}
+
+int
+openfile(Win *w, char *f)
+{
+ char buf[64];
+ int fd;
+
+ sprint(buf, "/mnt/acme/%d/%s", w->winid, f);
+ fd = open(buf, ORDWR|OCEXEC);
+ if(fd < 0)
+ fprint (2,"can't open window %s file: %r", f);
+ return fd;
+}
+
+void
+openbody(Win *w, int mode)
+{
+ char buf[64];
+
+ sprint(buf, "/mnt/acme/%d/body", w->winid);
+ w->body = Bopen(buf, mode|OCEXEC);
+ if(w->body == nil)
+ fprint(2,"can't open window body file: %r");
+}
+
+void
+wwritebody(Win *w, char *s, int n)
+{
+ if(w->body == nil)
+ openbody(w, OWRITE);
+ if(Bwrite(w->body, s, n) != n)
+ fprint(2,"write error to window: %r");
+ Bflush(w->body);
+}
+
+void
+wreplace(Win *w, char *addr, char *repl, int nrepl)
+{
+ if(w->addr < 0)
+ w->addr = openfile(w, "addr");
+ if(w->data < 0)
+ w->data = openfile(w, "data");
+ if(write(w->addr, addr, strlen(addr)) < 0){
+ fprint(2, "mail: warning: badd address %s:%r\n", addr);
+ return;
+ }
+ if(write(w->data, repl, nrepl) != nrepl)
+ fprint(2, "writing data: %r");
+}
+
+static int
+nrunes(char *s, int nb)
+{
+ int i, n;
+ Rune r;
+
+ n = 0;
+ for(i=0; i<nb; n++)
+ i += chartorune(&r, s+i);
+ return n;
+}
+
+void
+wread(Win *w, uint q0, uint q1, char *data)
+{
+ int m, n, nr;
+ char buf[256];
+
+ if(w->addr < 0)
+ w->addr = openfile(w, "addr");
+ if(w->data < 0)
+ w->data = openfile(w, "data");
+ m = q0;
+ while(m < q1){
+ n = sprint(buf, "#%d", m);
+ if(write(w->addr, buf, n) != n)
+ fprint(2,"writing addr: %r");
+ n = read(w->data, buf, sizeof buf);
+ if(n <= 0)
+ fprint(2,"reading data: %r");
+ nr = nrunes(buf, n);
+ while(m+nr >q1){
+ do; while(n>0 && (buf[--n]&0xC0)==0x80);
+ --nr;
+ }
+ if(n == 0)
+ break;
+ memmove(data, buf, n);
+ data += n;
+ *data = 0;
+ m += nr;
+ }
+}
+
+void
+wselect(Win *w, char *addr)
+{
+ if(w->addr < 0)
+ w->addr = openfile(w, "addr");
+ if(write(w->addr, addr, strlen(addr)) < 0)
+ fprint(2,"writing addr");
+ ctlwrite(w, "dot=addr\n");
+}
+
+void
+wtagwrite(Win *w, char *s, int n)
+{
+ int fd;
+
+ fd = openfile(w, "tag");
+ if(write(fd, s, n) != n)
+ fprint(2,"tag write: %r");
+ close(fd);
+}
+
+void
+ctlwrite(Win *w, char *s)
+{
+ int n;
+
+ n = strlen(s);
+ if(write(w->ctl, s, n) != n)
+ fprint(2,"write error to ctl file: %r");
+}
+
+int
+wdel(Win *w)
+{
+ if(write(w->ctl, "del\n", 4) != 4)
+ return False;
+ wdormant(w);
+ close(w->ctl);
+ w->ctl = -1;
+ close(w->event);
+ w->event = -1;
+ return True;
+}
+
+void
+wname(Win *w, char *s)
+{
+ char buf[128];
+
+ sprint(buf, "name %s\n", s);
+ ctlwrite(w, buf);
+}
+
+void
+wclean(Win *w)
+{
+ if(w->body)
+ Bflush(w->body);
+ ctlwrite(w, "clean\n");
+}
+
+void
+wdormant(Win *w)
+{
+ if(w->addr >= 0){
+ close(w->addr);
+ w->addr = -1;
+ }
+ if(w->body != nil){
+ Bterm(w->body);
+ w->body = nil;
+ }
+ if(w->data >= 0){
+ close(w->data);
+ w->data = -1;
+ }
+}
+
+int
+getec(Win *w)
+{
+ if(w->nbuf == 0){
+ w->nbuf = read(w->event, w->buf, sizeof w->buf);
+ if(w->nbuf <= 0)
+ fprint(2,"event read error: %r");
+ w->bufp = w->buf;
+ }
+ w->nbuf--;
+ return *w->bufp++;
+}
+
+int
+geten(Win *w)
+{
+ int n, c;
+
+ n = 0;
+ while('0'<=(c=getec(w)) && c<='9')
+ n = n*10+(c-'0');
+ if(c != ' ')
+ fprint(2, "event number syntax");
+ return n;
+}
+
+int
+geter(Win *w, char *buf, int *nb)
+{
+ Rune r;
+ int n;
+
+ r = getec(w);
+ buf[0] = r;
+ n = 1;
+ if(r < Runeself)
+ goto Return;
+ while(!fullrune(buf, n))
+ buf[n++] = getec(w);
+ chartorune(&r, buf);
+ Return:
+ *nb = n;
+ return r;
+}
+
+
+void
+wevent(Win *w, Event *e)
+{
+ int i, nb;
+
+ e->c1 = getec(w);
+ e->c2 = getec(w);
+ e->q0 = geten(w);
+ e->q1 = geten(w);
+ e->flag = geten(w);
+ e->nr = geten(w);
+ if(e->nr > EVENTSIZE)
+ fprint(2, "wevent: event string too long");
+ e->nb = 0;
+ for(i=0; i<e->nr; i++){
+ e->r[i] = geter(w, e->b+e->nb, &nb);
+ e->nb += nb;
+ }
+ e->r[e->nr] = 0;
+ e->b[e->nb] = 0;
+ if(getec(w) != '\n')
+ fprint(2, "wevent: event syntax 2");
+}
+
+void
+wslave(Win *w, Channel *ce)
+{
+ Event e;
+
+ while(recv(ce, &e) >= 0)
+ wevent(w, &e);
+}
+
+void
+wwriteevent(Win *w, Event *e)
+{
+ fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
+}
+
+int
+wreadall(Win *w, char **sp)
+{
+ char *s;
+ int m, na, n;
+
+ if(w->body != nil)
+ Bterm(w->body);
+ openbody(w, OREAD);
+ s = nil;
+ na = 0;
+ n = 0;
+ for(;;){
+ if(na < n+512){
+ na += 1024;
+ s = erealloc(s, na+1);
+ }
+ m = Bread(w->body, s+n, na-n);
+ if(m <= 0)
+ break;
+ n += m;
+ }
+ s[n] = 0;
+ Bterm(w->body);
+ w->body = nil;
+ *sp = s;
+ return n;
+}
--- /dev/null
+enum
+{
+ False,
+ True,
+ EVENTSIZE=256,
+};
+
+
+typedef struct Event Event;
+struct Event
+{
+ int c1;
+ int c2;
+ int q0;
+ int q1;
+ int flag;
+ int nb;
+ int nr;
+ char b[EVENTSIZE*UTFmax+1];
+ Rune r[EVENTSIZE+1];
+};
+
+
+typedef struct Win Win;
+struct Win
+{
+ int winid;
+ int addr;
+ Biobuf *body;
+ int ctl;
+ int data;
+ int event;
+ char buf[512];
+ char *bufp;
+ int nbuf;
+};
+
+int dead(Win*);
+void wnew(Win*);
+void wwritebody(Win*, char *s, int n);
+void wread(Win*, uint, uint, char*);
+void wclean(Win*);
+void wname(Win*, char*);
+void wdormant(Win*);
+void wevent(Win*, Event*);
+void wtagwrite(Win*, char*, int);
+void wwriteevent(Win*, Event*);
+void wslave(Win*, Channel*); /* chan(Event) */
+void wreplace(Win*, char*, char*, int);
+void wselect(Win*, char*);
+int wdel(Win*);
+int wreadall(Win*, char**);
+
+void ctlwrite(Win*, char*);
+int getec(Win*);
+int geten(Win*);
+int geter(Win*, char*, int*);
+int openfile(Win*, char*);
+void openbody(Win*, int);
--- /dev/null
+</$objtype/mkfile
+
+TARG=\
+ mkwnew\
+ spout\
+
+OFILES=
+HFILES=
+LIB=
+
+DIRS=win
+
+BIN=../$objtype
+
+</sys/src/cmd/mkmany
+
+all:V: all.dirs
+install:V: install.dirs
+clean:V: clean.dirs
+nuke:V: nuke.dirs
+
+%.dirs:VQ:
+ for (i in $DIRS) @{
+ echo mk $i
+ cd $i
+ mk $stem
+ }
--- /dev/null
+#include <u.h>
+#include <libc.h>
+
+void
+main(int argc, char *argv[])
+{
+ int i, fd, pid, n;
+ char wdir[256];
+ int dflag;
+
+ dflag = 0;
+ ARGBEGIN{
+ case 'd':
+ dflag = 1;
+ break;
+ default:
+ fprint(2, "usage: wnew [-d] [label]\n");
+ }ARGEND
+
+ pid = getpid();
+ wdir[0] = '\0';
+ if(!dflag)
+ getwd(wdir, sizeof wdir);
+ if(argc>0)
+ for(i=0; i<argc; i++)
+ snprint(wdir, sizeof wdir, "%s%c%s", wdir, i==0? '/' : '-', argv[i]);
+ else
+ snprint(wdir, sizeof wdir, "%s/-win", wdir);
+
+ if((fd = open("/dev/wnew", ORDWR)) < 0)
+ sysfatal("wnew: can't open /dev/wnew: %r");
+
+ if(fprint(fd, "%d %s", pid, wdir+dflag) < 0)
+ sysfatal("wnew: can't create window: %r");
+
+ if(seek(fd, 0, 0) != 0)
+ sysfatal("wnew: can't seek: %r");
+
+ if((n=read(fd, wdir, sizeof wdir-1)) < 0)
+ sysfatal("wnew: can't read window id: %r");
+ wdir[n] = '\0';
+
+ print("%s\n", wdir);
+ exits(nil);
+}
--- /dev/null
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <bio.h>
+
+void spout(int, char*);
+
+Biobuf bout;
+
+void
+main(int argc, char *argv[])
+{
+ int i, fd;
+
+ Binit(&bout, 1, OWRITE);
+ if(argc == 1)
+ spout(0, "");
+ else
+ for(i=1; i<argc; i++){
+ fd = open(argv[i], OREAD);
+ if(fd < 0){
+ fprint(2, "spell: can't open %s: %r\n", argv[i]);
+ continue;
+ }
+ spout(fd, argv[i]);
+ close(fd);
+ }
+ exits(nil);
+}
+
+Biobuf b;
+
+void
+spout(int fd, char *name)
+{
+ char *s, *t, *w;
+ Rune r;
+ int inword, wordchar;
+ int n, wn, wid, c, m;
+ char buf[1024];
+
+ Binit(&b, fd, OREAD);
+ n = 0;
+ wn = 0;
+ while((s = Brdline(&b, '\n')) != nil){
+ if(s[0] == '.')
+ for(c=0; c<3 && *s>' '; c++){
+ n++;
+ s++;
+ }
+ inword = 0;
+ w = s;
+ t = s;
+ do{
+ c = *(uchar*)t;
+ if(c < Runeself)
+ wid = 1;
+ else{
+ wid = chartorune(&r, t);
+ c = r;
+ }
+ wordchar = 0;
+ if(isalpha(c))
+ wordchar = 1;
+ if(inword && !wordchar){
+ if(c=='\'' && isalpha(t[1]))
+ goto Continue;
+ m = t-w;
+ if(m > 1){
+ memmove(buf, w, m);
+ buf[m] = 0;
+ Bprint(&bout, "%s:#%d,#%d:%s\n", name, wn, n, buf);
+ }
+ inword = 0;
+ }else if(!inword && wordchar){
+ wn = n;
+ w = t;
+ inword = 1;
+ }
+ if(c=='\\' && (isalpha(t[1]) || t[1]=='(')){
+ switch(t[1]){
+ case '(':
+ m = 4;
+ break;
+ case 'f':
+ if(t[2] == '(')
+ m = 5;
+ else
+ m = 3;
+ break;
+ case 's':
+ if(t[2] == '+' || t[2]=='-'){
+ if(t[3] == '(')
+ m = 6;
+ else
+ m = 4;
+ }else{
+ if(t[2] == '(')
+ m = 5;
+ else if(t[2]=='1' || t[2]=='2' || t[2]=='3')
+ m = 4;
+ else
+ m = 3;
+ }
+ break;
+ default:
+ m = 2;
+ }
+ while(m-- > 0){
+ if(*t == '\n')
+ break;
+ n++;
+ t++;
+ }
+ continue;
+ }
+ Continue:
+ n++;
+ t += wid;
+ }while(c != '\n');
+ }
+ Bterm(&b);
+}
--- /dev/null
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "dat.h"
+
+Channel *fschan;
+Channel *writechan;
+
+static File *devcons, *devnew;
+
+static void
+fsread(Req *r)
+{
+ Fsevent e;
+
+ if(r->fid->file == devnew){
+ if(r->fid->aux==nil){
+ respond(r, "phase error");
+ return;
+ }
+ readstr(r, r->fid->aux);
+ respond(r, nil);
+ return;
+ }
+
+ assert(r->fid->file == devcons);
+ e.type = 'r';
+ e.r = r;
+ send(fschan, &e);
+}
+
+static void
+fsflush(Req *r)
+{
+ Fsevent e;
+
+ e.type = 'f';
+ e.r = r;
+ send(fschan, &e);
+}
+
+static void
+fswrite(Req *r)
+{
+ static Event *e[4];
+ Event *ep;
+ int i, j, nb, wid, pid;
+ Rune rune;
+ char *s;
+ char tmp[UTFmax], *t;
+ static int n, partial;
+
+ if(r->fid->file == devnew){
+ if(r->fid->aux){
+ respond(r, "already created a window");
+ return;
+ }
+ s = emalloc(r->ifcall.count+1);
+ memmove(s, r->ifcall.data, r->ifcall.count);
+ s[r->ifcall.count] = 0;
+ pid = strtol(s, &t, 0);
+ if(*t==' ')
+ t++;
+ i = newpipewin(pid, t);
+ free(s);
+ s = emalloc(32);
+ sprint(s, "%lud", (ulong)i);
+ r->fid->aux = s;
+ r->ofcall.count = r->ifcall.count;
+ respond(r, nil);
+ return;
+ }
+
+ assert(r->fid->file == devcons);
+
+ if(e[0] == nil){
+ for(i=0; i<nelem(e); i++){
+ e[i] = emalloc(sizeof(Event));
+ e[i]->c1 = 'S';
+ }
+ }
+
+ ep = e[n];
+ n = (n+1)%nelem(e);
+ assert(r->ifcall.count <= 8192); /* is this guaranteed by lib9p? */
+ nb = r->ifcall.count;
+ memmove(ep->b+partial, r->ifcall.data, nb);
+ nb += partial;
+ ep->b[nb] = '\0';
+ if(strlen(ep->b) < nb){ /* nulls in data */
+ t = ep->b;
+ for(i=j=0; i<nb; i++)
+ if(ep->b[i] != '\0')
+ t[j++] = ep->b[i];
+ nb = j;
+ t[j] = '\0';
+ }
+ /* process bytes into runes, transferring terminal partial runes into next buffer */
+ for(i=j=0; i<nb && fullrune(ep->b+i, nb-i); i+=wid,j++)
+ wid = chartorune(&rune, ep->b+i);
+ memmove(tmp, ep->b+i, nb-i);
+ partial = nb-i;
+ ep->nb = i;
+ ep->nr = j;
+ ep->b[i] = '\0';
+ if(i != 0){
+ sendp(win->cevent, ep);
+ recvp(writechan);
+ }
+ partial = nb-i;
+ memmove(e[n]->b, tmp, partial);
+ r->ofcall.count = r->ifcall.count;
+ respond(r, nil);
+}
+
+void
+fsdestroyfid(Fid *fid)
+{
+ if(fid->aux)
+ free(fid->aux);
+}
+
+Srv fs = {
+.read= fsread,
+.write= fswrite,
+.flush= fsflush,
+.destroyfid= fsdestroyfid,
+.leavefdsopen= 1,
+};
+
+void
+mountcons(void)
+{
+ fschan = chancreate(sizeof(Fsevent), 0);
+ writechan = chancreate(sizeof(void*), 0);
+ fs.tree = alloctree("win", "win", DMDIR|0555, nil);
+ devcons = createfile(fs.tree->root, "cons", "win", 0666, nil);
+ if(devcons == nil)
+ sysfatal("creating /dev/cons: %r");
+ devnew = createfile(fs.tree->root, "wnew", "win", 0666, nil);
+ if(devnew == nil)
+ sysfatal("creating /dev/wnew: %r");
+ threadpostmountsrv(&fs, nil, "/dev", MBEFORE);
+}
--- /dev/null
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <fcall.h>
+#include <9p.h>
+#include <ctype.h>
+#include "dat.h"
+
+void mainctl(void*);
+void startcmd(char *[], int*);
+void stdout2body(void*);
+
+int debug;
+int notepg;
+int eraseinput;
+int dirty = 0;
+
+Window *win; /* the main window */
+
+void
+usage(void)
+{
+ fprint(2, "usage: win [command]\n");
+ threadexitsall("usage");
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ int i, j;
+ char *dir, *tag, *name;
+ char buf[1024], **av;
+
+ quotefmtinstall();
+ rfork(RFNAMEG);
+ ARGBEGIN{
+ case 'd':
+ debug = 1;
+ chatty9p++;
+ break;
+ case 'e':
+ eraseinput = 1;
+ break;
+ case 'D':
+{extern int _threaddebuglevel;
+ _threaddebuglevel = 1<<20;
+}
+ }ARGEND
+
+ if(argc == 0){
+ av = emalloc(3*sizeof(char*));
+ av[0] = "rc";
+ av[1] = "-i";
+ name = getenv("sysname");
+ }else{
+ av = argv;
+ name = utfrrune(av[0], '/');
+ if(name)
+ name++;
+ else
+ name = av[0];
+ }
+
+ if(getwd(buf, sizeof buf) == 0)
+ dir = "/";
+ else
+ dir = buf;
+ dir = estrdup(dir);
+ tag = estrdup(dir);
+ tag = eappend(estrdup(tag), "/-", name);
+ win = newwindow();
+ snprint(buf, sizeof buf, "%d", win->id);
+ putenv("winid", buf);
+ winname(win, tag);
+ wintagwrite(win, "Send Noscroll", 5+8);
+ threadcreate(mainctl, win, STACK);
+ mountcons();
+ threadcreate(fsloop, nil, STACK);
+ startpipe();
+ startcmd(av, ¬epg);
+
+ strcpy(buf, "win");
+ j = 3;
+ for(i=0; i<argc && j+1+strlen(argv[i])+1<sizeof buf; i++){
+ strcpy(buf+j, " ");
+ strcpy(buf+j+1, argv[i]);
+ j += 1+strlen(argv[i]);
+ }
+
+ ctlprint(win->ctl, "scroll");
+ winsetdump(win, dir, buf);
+}
+
+int
+EQUAL(char *s, char *t)
+{
+ while(tolower(*s) == tolower(*t++))
+ if(*s++ == '\0')
+ return 1;
+ return 0;
+}
+
+int
+command(Window *w, char *s)
+{
+ while(*s==' ' || *s=='\t' || *s=='\n')
+ s++;
+ if(strcmp(s, "Delete")==0){
+ windel(w, 1);
+ threadexitsall(nil);
+ return 1;
+ }
+ if(strcmp(s, "Del")==0){
+ if(windel(w, 0))
+ threadexitsall(nil);
+ return 1;
+ }
+ if(EQUAL(s, "scroll")){
+ ctlprint(w->ctl, "scroll\nshow");
+ return 1;
+ }
+ if(EQUAL(s, "noscroll")){
+ ctlprint(w->ctl, "noscroll");
+ return 1;
+ }
+ return 0;
+}
+
+static long
+utfncpy(char *to, char *from, int n)
+{
+ char *end, *e;
+
+ e = to+n;
+ if(to >= e)
+ return 0;
+ end = memccpy(to, from, '\0', e - to);
+ if(end == nil){
+ end = e;
+ if(end[-1]&0x80){
+ if(end-2>=to && (end[-2]&0xE0)==0xC0)
+ return end-to;
+ if(end-3>=to && (end[-3]&0xF0)==0xE0)
+ return end-to;
+ while(end>to && (*--end&0xC0)==0x80)
+ ;
+ }
+ }else
+ end--;
+ return end - to;
+}
+
+/* sendinput and fsloop run in the same proc (can't interrupt each other). */
+static Req *q;
+static Req **eq;
+static int
+__sendinput(Window *w, ulong q0, ulong q1)
+{
+ char *s, *t;
+ int n, nb, eofchar;
+ static int partial;
+ static char tmp[UTFmax];
+ Req *r;
+ Rune rune;
+
+ if(!q)
+ return 0;
+
+ r = q;
+ n = 0;
+ if(partial){
+ Partial:
+ nb = partial;
+ if(nb > r->ifcall.count)
+ nb = r->ifcall.count;
+ memmove(r->ofcall.data, tmp, nb);
+ if(nb!=partial)
+ memmove(tmp, tmp+nb, partial-nb);
+ partial -= nb;
+ q = r->aux;
+ if(q == nil)
+ eq = &q;
+ r->aux = nil;
+ r->ofcall.count = nb;
+ if(debug)
+ fprint(2, "satisfy read with partial\n");
+ respond(r, nil);
+ return n;
+ }
+ if(q0==q1)
+ return 0;
+ s = emalloc((q1-q0)*UTFmax+1);
+ n = winread(w, q0, q1, s);
+ s[n] = '\0';
+ t = strpbrk(s, "\n\004");
+ if(t == nil){
+ free(s);
+ return 0;
+ }
+ r = q;
+ eofchar = 0;
+ if(*t == '\004'){
+ eofchar = 1;
+ *t = '\0';
+ }else
+ *++t = '\0';
+ nb = utfncpy((char*)r->ofcall.data, s, r->ifcall.count);
+ if(nb==0 && s<t && r->ifcall.count > 0){
+ partial = utfncpy(tmp, s, UTFmax);
+ assert(partial > 0);
+ chartorune(&rune, tmp);
+ partial = runelen(rune);
+ free(s);
+ n = 1;
+ goto Partial;
+ }
+ n = utfnlen(r->ofcall.data, nb);
+ if(nb==strlen(s) && eofchar)
+ n++;
+ r->ofcall.count = nb;
+ q = r->aux;
+ if(q == nil)
+ eq = &q;
+ r->aux = nil;
+ if(debug)
+ fprint(2, "read returns %lud-%lud: %.*q\n", q0, q0+n, n, r->ofcall.data);
+ respond(r, nil);
+ return n;
+}
+
+static int
+_sendinput(Window *w, ulong q0, ulong *q1)
+{
+ char buf[32];
+ int n;
+
+ n = __sendinput(w, q0, *q1);
+ if(!n || !eraseinput)
+ return n;
+ /* erase q0 to q0+n */
+ sprint(buf, "#%lud,#%lud", q0, q0+n);
+ winsetaddr(w, buf, 0);
+ write(w->data, buf, 0);
+ *q1 -= n;
+ return 0;
+}
+
+int
+sendinput(Window *w, ulong q0, ulong *q1)
+{
+ ulong n;
+ Req *oq;
+
+ n = 0;
+ do {
+ oq = q;
+ n += _sendinput(w, q0+n, q1);
+ } while(q != oq);
+ return n;
+}
+
+Event esendinput;
+void
+fsloop(void*)
+{
+ Fsevent e;
+ Req **l, *r;
+
+ eq = &q;
+ memset(&esendinput, 0, sizeof esendinput);
+ esendinput.c1 = 'C';
+ for(;;){
+ while(recv(fschan, &e) == -1)
+ ;
+ r = e.r;
+ switch(e.type){
+ case 'r':
+ *eq = r;
+ r->aux = nil;
+ eq = &r->aux;
+ /* call sendinput with hostpt and endpt */
+ sendp(win->cevent, &esendinput);
+ break;
+ case 'f':
+ for(l=&q; *l; l=&(*l)->aux){
+ if(*l == r->oldreq){
+ *l = (*l)->aux;
+ if(*l == nil)
+ eq = l;
+ respond(r->oldreq, "interrupted");
+ break;
+ }
+ }
+ respond(r, nil);
+ break;
+ }
+ }
+}
+
+void
+sendit(char *s)
+{
+// char tmp[32];
+
+ write(win->body, s, strlen(s));
+/*
+ * RSC: The problem here is that other procs can call sendit,
+ * so we lose our single-threadedness if we call sendinput.
+ * In fact, we don't even have the right queue memory,
+ * I think that we'll get a write event from the body write above,
+ * and we can do the sendinput then, from our single thread.
+ *
+ * I still need to figure out how to test this assertion for
+ * programs that use /srv/win*
+ *
+ winselect(win, "$", 0);
+ seek(win->addr, 0UL, 0);
+ if(read(win->addr, tmp, 2*12) == 2*12)
+ hostpt += sendinput(win, hostpt, atol(tmp), );
+ */
+}
+
+void
+execevent(Window *w, Event *e, int (*command)(Window*, char*))
+{
+ Event *ea, *e2;
+ int n, na, len, needfree;
+ char *s, *t;
+
+ ea = nil;
+ e2 = nil;
+ if(e->flag & 2)
+ e2 = recvp(w->cevent);
+ if(e->flag & 8){
+ ea = recvp(w->cevent);
+ na = ea->nb;
+ recvp(w->cevent);
+ }else
+ na = 0;
+
+ needfree = 0;
+ s = e->b;
+ if(e->nb==0 && (e->flag&2)){
+ s = e2->b;
+ e->q0 = e2->q0;
+ e->q1 = e2->q1;
+ e->nb = e2->nb;
+ }
+ if(e->nb==0 && e->q0<e->q1){
+ /* fetch data from window */
+ s = emalloc((e->q1-e->q0)*UTFmax+2);
+ n = winread(w, e->q0, e->q1, s);
+ s[n] = '\0';
+ needfree = 1;
+ }else
+ if(na){
+ t = emalloc(strlen(s)+1+na+2);
+ sprint(t, "%s %s", s, ea->b);
+ if(needfree)
+ free(s);
+ s = t;
+ needfree = 1;
+ }
+
+ /* if it's a known command, do it */
+ /* if it's a long message, it can't be for us anyway */
+ if(!command(w, s) && s[0]!='\0'){ /* send it as typed text */
+ /* if it's a built-in from the tag, send it back */
+ if(e->flag & 1)
+ fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
+ else{ /* send text to main window */
+ len = strlen(s);
+ if(len>0 && s[len-1]!='\n' && s[len-1]!='\004'){
+ if(!needfree){
+ /* if(needfree), we left room for a newline before */
+ t = emalloc(len+2);
+ strcpy(t, s);
+ s = t;
+ needfree = 1;
+ }
+ s[len++] = '\n';
+ s[len] = '\0';
+ }
+ sendit(s);
+ }
+ }
+ if(needfree)
+ free(s);
+}
+
+int
+hasboundary(Rune *r, int nr)
+{
+ int i;
+
+ for(i=0; i<nr; i++)
+ if(r[i]=='\n' || r[i]=='\004')
+ return 1;
+ return 0;
+}
+
+void
+mainctl(void *v)
+{
+ Window *w;
+ Event *e;
+ int delta, pendingS, pendingK;
+ ulong hostpt, endpt;
+ char tmp[32];
+
+ w = v;
+ proccreate(wineventproc, w, STACK);
+
+ hostpt = 0;
+ endpt = 0;
+ winsetaddr(w, "0", 0);
+ pendingS = 0;
+ pendingK = 0;
+ for(;;){
+ if(debug)
+ fprint(2, "input range %lud-%lud\n", hostpt, endpt);
+ e = recvp(w->cevent);
+ if(debug)
+ fprint(2, "msg: %C %C %d %d %d %d %q\n",
+ e->c1 ? e->c1 : ' ', e->c2 ? e->c2 : ' ', e->q0, e->q1, e->flag, e->nb, e->b);
+ switch(e->c1){
+ default:
+ Unknown:
+ fprint(2, "unknown message %c%c\n", e->c1, e->c2);
+ break;
+
+ case 'C': /* input needed for /dev/cons */
+ if(pendingS)
+ pendingK = 1;
+ else
+ hostpt += sendinput(w, hostpt, &endpt);
+ break;
+
+ case 'S': /* output to stdout */
+ sprint(tmp, "#%lud", hostpt);
+ winsetaddr(w, tmp, 0);
+ write(w->data, e->b, e->nb);
+ pendingS += utfnlen(e->b, e->nb);
+ break;
+
+ case 'E': /* write to tag or body; body happens due to sendit */
+ delta = e->q1-e->q0;
+ if(e->c2=='I'){
+ endpt += delta;
+ if(e->q0 < hostpt)
+ hostpt += delta;
+ else
+ hostpt += sendinput(w, hostpt, &endpt);
+ break;
+ }
+ if(!islower(e->c2))
+ fprint(2, "win msg: %C %C %d %d %d %d %q\n",
+ e->c1, e->c2, e->q0, e->q1, e->flag, e->nb, e->b);
+ break;
+
+ case 'F': /* generated by our actions (specifically case 'S' above) */
+ delta = e->q1-e->q0;
+ if(e->c2=='D'){
+ /* we know about the delete by _sendinput */
+ break;
+ }
+ if(e->c2=='I'){
+ pendingS -= e->q1 - e->q0;
+ if(pendingS < 0)
+ fprint(2, "win: pendingS = %d\n", pendingS);
+ if(e->q0 != hostpt)
+ fprint(2, "win: insert at %d expected %lud\n", e->q0, hostpt);
+ endpt += delta;
+ hostpt += delta;
+ sendp(writechan, nil);
+ if(pendingS == 0 && pendingK){
+ pendingK = 0;
+ hostpt += sendinput(w, hostpt, &endpt);
+ }
+ break;
+ }
+ if(!islower(e->c2))
+ fprint(2, "win msg: %C %C %d %d %d %d %q\n",
+ e->c1, e->c2, e->q0, e->q1, e->flag, e->nb, e->b);
+ break;
+
+ case 'K':
+ delta = e->q1-e->q0;
+ switch(e->c2){
+ case 'D':
+ endpt -= delta;
+ if(e->q1 < hostpt)
+ hostpt -= delta;
+ else if(e->q0 < hostpt)
+ hostpt = e->q0;
+ break;
+ case 'I':
+ delta = e->q1 - e->q0;
+ endpt += delta;
+ if(endpt < e->q1) /* just in case */
+ endpt = e->q1;
+ if(e->q0 < hostpt)
+ hostpt += delta;
+ if(e->nr>0 && e->r[e->nr-1]==0x7F){
+ write(notepg, "interrupt", 9);
+ hostpt = endpt;
+ break;
+ }
+ if(e->q0 >= hostpt
+ && hasboundary(e->r, e->nr)){
+ /*
+ * If we are between the S message (which
+ * we processed by inserting text in the
+ * window) and the F message notifying us
+ * that the text has been inserted, then our
+ * impression of the hostpt and acme's
+ * may be different. This could be seen if you
+ * hit enter a bunch of times in a con
+ * session. To work around the unreliability,
+ * only send input if we don't have an S pending.
+ * The same race occurs between when a character
+ * is typed and when we get notice of it, but
+ * since characters tend to be typed at the end
+ * of the buffer, we don't run into it. There's
+ * no workaround possible for this typing race,
+ * since we can't tell when the user has typed
+ * something but we just haven't been notified.
+ */
+ if(pendingS)
+ pendingK = 1;
+ else
+ hostpt += sendinput(w, hostpt, &endpt);
+ }
+ break;
+ }
+ break;
+
+ case 'M': /* mouse */
+ delta = e->q1-e->q0;
+ switch(e->c2){
+ case 'x':
+ case 'X':
+ execevent(w, e, command);
+ break;
+
+ case 'l': /* reflect all searches back to acme */
+ case 'L':
+ if(e->flag & 2)
+ recvp(w->cevent);
+ winwriteevent(w, e);
+ break;
+
+ case 'I':
+ endpt += delta;
+ if(e->q0 < hostpt)
+ hostpt += delta;
+ else
+ hostpt += sendinput(w, hostpt, &endpt);
+ break;
+
+ case 'D':
+ endpt -= delta;
+ if(e->q1 < hostpt)
+ hostpt -= delta;
+ else if(e->q0 < hostpt)
+ hostpt = e->q0;
+ break;
+ case 'd': /* modify away; we don't care */
+ case 'i':
+ break;
+
+ default:
+ goto Unknown;
+ }
+ }
+ }
+}
+
+enum
+{
+ NARGS = 100,
+ NARGCHAR = 8*1024,
+ EXECSTACK = STACK+(NARGS+1)*sizeof(char*)+NARGCHAR
+};
+
+struct Exec
+{
+ char **argv;
+ Channel *cpid;
+};
+
+int
+lookinbin(char *s)
+{
+ if(s[0] == '/')
+ return 0;
+ if(s[0]=='.' && s[1]=='/')
+ return 0;
+ if(s[0]=='.' && s[1]=='.' && s[2]=='/')
+ return 0;
+ return 1;
+}
+
+/* adapted from mail. not entirely free of details from that environment */
+void
+execproc(void *v)
+{
+ struct Exec *e;
+ char *cmd, **av;
+ Channel *cpid;
+
+ e = v;
+ rfork(RFCFDG|RFNOTEG);
+ av = e->argv;
+ close(0);
+ open("/dev/cons", OREAD);
+ close(1);
+ open("/dev/cons", OWRITE);
+ dup(1, 2);
+ cpid = e->cpid;
+ free(e);
+ procexec(cpid, av[0], av);
+ if(lookinbin(av[0])){
+ cmd = estrstrdup("/bin/", av[0]);
+ procexec(cpid, cmd, av);
+ }
+ error("can't exec %s: %r", av[0]);
+}
+
+void
+startcmd(char *argv[], int *notepg)
+{
+ struct Exec *e;
+ Channel *cpid;
+ char buf[64];
+ int pid;
+
+ e = emalloc(sizeof(struct Exec));
+ e->argv = argv;
+ cpid = chancreate(sizeof(ulong), 0);
+ e->cpid = cpid;
+ sprint(buf, "/mnt/wsys/%d", win->id);
+ bind(buf, "/dev/acme", MREPL);
+ proccreate(execproc, e, EXECSTACK);
+ do
+ pid = recvul(cpid);
+ while(pid == -1);
+ sprint(buf, "/proc/%d/notepg", pid);
+ *notepg = open(buf, OWRITE);
+}
--- /dev/null
+typedef struct Fsevent Fsevent;
+typedef struct Event Event;
+typedef struct Message Message;
+typedef struct Window Window;
+
+enum
+{
+ STACK = 8192,
+ NPIPEDATA = 8000,
+ NPIPE = NPIPEDATA+32,
+ /* EVENTSIZE is really 256 in acme, but we use events internally and want bigger buffers */
+ EVENTSIZE = 8192,
+ NEVENT = 5,
+};
+
+struct Fsevent
+{
+ int type;
+ void *r;
+};
+
+struct Event
+{
+ int c1;
+ int c2;
+ int q0;
+ int q1;
+ int flag;
+ int nb;
+ int nr;
+ char b[EVENTSIZE*UTFmax+1];
+ Rune r[EVENTSIZE+1];
+};
+
+struct Window
+{
+ /* file descriptors */
+ int ctl;
+ int event;
+ int addr;
+ int data;
+ int body;
+
+ /* event input */
+ char buf[512];
+ char *bufp;
+ int nbuf;
+ Event e[NEVENT];
+
+ int id;
+ int open;
+ Channel *cevent;
+};
+
+extern Window* newwindow(void);
+extern int winopenfile(Window*, char*);
+extern void wintagwrite(Window*, char*, int);
+extern void winname(Window*, char*);
+extern void winwriteevent(Window*, Event*);
+extern int winread(Window*, uint, uint, char*);
+extern int windel(Window*, int);
+extern void wingetevent(Window*, Event*);
+extern void wineventproc(void*);
+extern void winclean(Window*);
+extern int winselect(Window*, char*, int);
+extern int winsetaddr(Window*, char*, int);
+extern void windormant(Window*);
+extern void winsetdump(Window*, char*, char*);
+
+extern void ctlprint(int, char*, ...);
+extern void* emalloc(uint);
+extern char* estrdup(char*);
+extern char* estrstrdup(char*, char*);
+extern char* egrow(char*, char*, char*);
+extern char* eappend(char*, char*, char*);
+extern void error(char*, ...);
+
+extern void startpipe(void);
+extern void sendit(char*);
+extern void execevent(Window *w, Event *e, int (*)(Window*, char*));
+
+extern void mountcons(void);
+extern void fsloop(void*);
+
+extern int newpipewin(int, char*);
+extern void startpipe(void);
+extern int pipecommand(Window*, char*);
+extern void pipectl(void*);
+
+#pragma varargck argpos error 1
+#pragma varargck argpos ctlprint 2
+
+extern Window *win;
+extern Channel *fschan, *writechan;
+
--- /dev/null
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "dat.h"
+
+Channel *fschan;
+Channel *writechan;
+
+static File *devcons, *devnew;
+
+static void
+fsread(Req *r)
+{
+ Fsevent e;
+
+ if(r->fid->file == devnew){
+ if(r->fid->aux==nil){
+ respond(r, "phase error");
+ return;
+ }
+ readstr(r, r->fid->aux);
+ respond(r, nil);
+ return;
+ }
+
+ assert(r->fid->file == devcons);
+ e.type = 'r';
+ e.r = r;
+ send(fschan, &e);
+}
+
+static void
+fsflush(Req *r)
+{
+ Fsevent e;
+
+ e.type = 'f';
+ e.r = r;
+ send(fschan, &e);
+}
+
+static void
+fswrite(Req *r)
+{
+ static Event *e[4];
+ Event *ep;
+ int i, j, ei, nb, wid, pid;
+ Rune rune;
+ char *s;
+ char tmp[UTFmax], *t;
+ static int n, partial;
+
+ if(r->fid->file == devnew){
+ if(r->fid->aux){
+ respond(r, "already created a window");
+ return;
+ }
+ s = emalloc(r->ifcall.count+1);
+ memmove(s, r->ifcall.data, r->ifcall.count);
+ s[r->ifcall.count] = 0;
+ pid = strtol(s, &t, 0);
+ if(*t==' ')
+ t++;
+ i = newpipewin(pid, t);
+ free(s);
+ s = emalloc(32);
+ sprint(s, "%lud", (ulong)i);
+ r->fid->aux = s;
+ r->ofcall.count = r->ifcall.count;
+ respond(r, nil);
+ return;
+ }
+
+ assert(r->fid->file == devcons);
+
+ if(e[0] == nil){
+ for(i=0; i<nelem(e); i++){
+ e[i] = emalloc(sizeof(Event));
+ e[i]->c1 = 'S';
+ }
+ }
+
+ ep = e[n];
+ n = (n+1)%nelem(e);
+ assert(r->ifcall.count <= 8192); /* is this guaranteed by lib9p? */
+ nb = r->ifcall.count;
+ memmove(ep->b+partial, r->ifcall.data, nb);
+ nb += partial;
+ ep->b[nb] = '\0';
+ if(strlen(ep->b) < nb){ /* nulls in data */
+ t = ep->b;
+ for(i=j=0; i<nb; i++)
+ if(ep->b[i] != '\0')
+ t[j++] = ep->b[i];
+ nb = j;
+ t[j] = '\0';
+ }
+ ei = nb>8192? 8192 : nb;
+ /* process bytes into runes, transferring terminal partial runes into next buffer */
+ for(i=j=0; i<ei && fullrune(ep->b+i, ei-i); i+=wid,j++)
+ wid = chartorune(&rune, ep->b+i);
+ memmove(tmp, ep->b+i, nb-i);
+ partial = nb-i;
+ ep->nb = i;
+ ep->nr = j;
+ ep->b[i] = '\0';
+ if(i != 0){
+ sendp(win->cevent, ep);
+ recvp(writechan);
+ }
+ partial = nb-i;
+ memmove(e[n]->b, tmp, partial);
+ r->ofcall.count = r->ifcall.count;
+ respond(r, nil);
+}
+
+void
+fsdestroyfid(Fid *fid)
+{
+ if(fid->aux)
+ free(fid->aux);
+}
+
+Srv fs = {
+.read= fsread,
+.write= fswrite,
+.flush= fsflush,
+.destroyfid= fsdestroyfid,
+.leavefdsopen= 1,
+};
+
+void
+mountcons(void)
+{
+ fschan = chancreate(sizeof(Fsevent), 0);
+ writechan = chancreate(sizeof(void*), 0);
+ fs.tree = alloctree("win", "win", DMDIR|0555, nil);
+ devcons = createfile(fs.tree->root, "cons", "win", 0666, nil);
+ if(devcons == nil)
+ sysfatal("creating /dev/cons: %r");
+ devnew = createfile(fs.tree->root, "wnew", "win", 0666, nil);
+ if(devnew == nil)
+ sysfatal("creating /dev/wnew: %r");
+ threadpostmountsrv(&fs, nil, "/dev", MBEFORE);
+}
--- /dev/null
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <fcall.h>
+#include <9p.h>
+#include <ctype.h>
+#include "dat.h"
+
+void mainctl(void*);
+void startcmd(char *[], int*);
+void stdout2body(void*);
+
+int debug;
+int notepg;
+int eraseinput;
+int dirty = 0;
+
+Window *win; /* the main window */
+
+void
+usage(void)
+{
+ fprint(2, "usage: win [command]\n");
+ threadexitsall("usage");
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ int i, j;
+ char *dir, *tag, *name;
+ char buf[1024], **av;
+
+ quotefmtinstall();
+ rfork(RFNAMEG);
+ ARGBEGIN{
+ case 'd':
+ debug = 1;
+ chatty9p++;
+ break;
+ case 'e':
+ eraseinput = 1;
+ break;
+ case 'D':
+{extern int _threaddebuglevel;
+ _threaddebuglevel = 1<<20;
+}
+ }ARGEND
+
+ if(argc == 0){
+ av = emalloc(3*sizeof(char*));
+ av[0] = "rc";
+ av[1] = "-i";
+ name = getenv("sysname");
+ }else{
+ av = argv;
+ name = utfrrune(av[0], '/');
+ if(name)
+ name++;
+ else
+ name = av[0];
+ }
+
+ if(getwd(buf, sizeof buf) == 0)
+ dir = "/";
+ else
+ dir = buf;
+ dir = estrdup(dir);
+ tag = estrdup(dir);
+ tag = eappend(estrdup(tag), "/-", name);
+ win = newwindow();
+ snprint(buf, sizeof buf, "%d", win->id);
+ putenv("winid", buf);
+ winname(win, tag);
+ wintagwrite(win, "Send Noscroll", 5+8);
+ threadcreate(mainctl, win, STACK);
+ mountcons();
+ threadcreate(fsloop, nil, STACK);
+ startpipe();
+ startcmd(av, ¬epg);
+
+ strcpy(buf, "win");
+ j = 3;
+ for(i=0; i<argc && j+1+strlen(argv[i])+1<sizeof buf; i++){
+ strcpy(buf+j, " ");
+ strcpy(buf+j+1, argv[i]);
+ j += 1+strlen(argv[i]);
+ }
+
+ ctlprint(win->ctl, "scroll");
+ winsetdump(win, dir, buf);
+}
+
+int
+EQUAL(char *s, char *t)
+{
+ while(tolower(*s) == tolower(*t++))
+ if(*s++ == '\0')
+ return 1;
+ return 0;
+}
+
+int
+command(Window *w, char *s)
+{
+ while(*s==' ' || *s=='\t' || *s=='\n')
+ s++;
+ if(strcmp(s, "Delete")==0 || strcmp(s, "Del")==0){
+ windel(w, 1);
+ threadexitsall(nil);
+ return 1;
+ }
+ if(EQUAL(s, "scroll")){
+ ctlprint(w->ctl, "scroll\nshow");
+ return 1;
+ }
+ if(EQUAL(s, "noscroll")){
+ ctlprint(w->ctl, "noscroll");
+ return 1;
+ }
+ return 0;
+}
+
+static long
+utfncpy(char *to, char *from, int n)
+{
+ char *end, *e;
+
+ e = to+n;
+ if(to >= e)
+ return 0;
+ end = memccpy(to, from, '\0', e - to);
+ if(end == nil){
+ end = e;
+ if(end[-1]&0x80){
+ if(end-2>=to && (end[-2]&0xE0)==0xC0)
+ return end-to;
+ if(end-3>=to && (end[-3]&0xF0)==0xE0)
+ return end-to;
+ while(end>to && (*--end&0xC0)==0x80)
+ ;
+ }
+ }else
+ end--;
+ return end - to;
+}
+
+/* sendinput and fsloop run in the same proc (can't interrupt each other). */
+static Req *q;
+static Req **eq;
+static int
+__sendinput(Window *w, ulong q0, ulong q1)
+{
+ char *s, *t;
+ int n, nb, eofchar;
+ static int partial;
+ static char tmp[UTFmax];
+ Req *r;
+ Rune rune;
+
+ if(!q)
+ return 0;
+
+ r = q;
+ n = 0;
+ if(partial){
+ Partial:
+ nb = partial;
+ if(nb > r->ifcall.count)
+ nb = r->ifcall.count;
+ memmove(r->ofcall.data, tmp, nb);
+ if(nb!=partial)
+ memmove(tmp, tmp+nb, partial-nb);
+ partial -= nb;
+ q = r->aux;
+ if(q == nil)
+ eq = &q;
+ r->aux = nil;
+ r->ofcall.count = nb;
+ if(debug)
+ fprint(2, "satisfy read with partial\n");
+ respond(r, nil);
+ return n;
+ }
+ if(q0==q1)
+ return 0;
+ s = emalloc((q1-q0)*UTFmax+1);
+ n = winread(w, q0, q1, s);
+ s[n] = '\0';
+ t = strpbrk(s, "\n\004");
+ if(t == nil){
+ free(s);
+ return 0;
+ }
+ r = q;
+ eofchar = 0;
+ if(*t == '\004'){
+ eofchar = 1;
+ *t = '\0';
+ }else
+ *++t = '\0';
+ nb = utfncpy((char*)r->ofcall.data, s, r->ifcall.count);
+ if(nb==0 && s<t && r->ifcall.count > 0){
+ partial = utfncpy(tmp, s, UTFmax);
+ assert(partial > 0);
+ chartorune(&rune, tmp);
+ partial = runelen(rune);
+ free(s);
+ n = 1;
+ goto Partial;
+ }
+ n = utfnlen(r->ofcall.data, nb);
+ if(nb==strlen(s) && eofchar)
+ n++;
+ r->ofcall.count = nb;
+ q = r->aux;
+ if(q == nil)
+ eq = &q;
+ r->aux = nil;
+ if(debug)
+ fprint(2, "read returns %lud-%lud: %.*q\n", q0, q0+n, n, r->ofcall.data);
+ respond(r, nil);
+ return n;
+}
+
+static int
+_sendinput(Window *w, ulong q0, ulong *q1)
+{
+ char buf[32];
+ int n;
+
+ n = __sendinput(w, q0, *q1);
+ if(!n || !eraseinput)
+ return n;
+ /* erase q0 to q0+n */
+ sprint(buf, "#%lud,#%lud", q0, q0+n);
+ winsetaddr(w, buf, 0);
+ write(w->data, buf, 0);
+ *q1 -= n;
+ return 0;
+}
+
+int
+sendinput(Window *w, ulong q0, ulong *q1)
+{
+ ulong n;
+ Req *oq;
+
+ n = 0;
+ do {
+ oq = q;
+ n += _sendinput(w, q0+n, q1);
+ } while(q != oq);
+ return n;
+}
+
+Event esendinput;
+void
+fsloop(void*)
+{
+ Fsevent e;
+ Req **l, *r;
+
+ eq = &q;
+ memset(&esendinput, 0, sizeof esendinput);
+ esendinput.c1 = 'C';
+ for(;;){
+ while(recv(fschan, &e) == -1)
+ ;
+ r = e.r;
+ switch(e.type){
+ case 'r':
+ *eq = r;
+ r->aux = nil;
+ eq = &r->aux;
+ /* call sendinput with hostpt and endpt */
+ sendp(win->cevent, &esendinput);
+ break;
+ case 'f':
+ for(l=&q; *l; l=&(*l)->aux){
+ if(*l == r->oldreq){
+ *l = (*l)->aux;
+ if(*l == nil)
+ eq = l;
+ respond(r->oldreq, "interrupted");
+ break;
+ }
+ }
+ respond(r, nil);
+ break;
+ }
+ }
+}
+
+void
+sendit(char *s)
+{
+// char tmp[32];
+
+ write(win->body, s, strlen(s));
+/*
+ * RSC: The problem here is that other procs can call sendit,
+ * so we lose our single-threadedness if we call sendinput.
+ * In fact, we don't even have the right queue memory,
+ * I think that we'll get a write event from the body write above,
+ * and we can do the sendinput then, from our single thread.
+ *
+ * I still need to figure out how to test this assertion for
+ * programs that use /srv/win*
+ *
+ winselect(win, "$", 0);
+ seek(win->addr, 0UL, 0);
+ if(read(win->addr, tmp, 2*12) == 2*12)
+ hostpt += sendinput(win, hostpt, atol(tmp), );
+ */
+}
+
+void
+execevent(Window *w, Event *e, int (*command)(Window*, char*))
+{
+ Event *ea, *e2;
+ int n, na, len, needfree;
+ char *s, *t;
+
+ ea = nil;
+ e2 = nil;
+ if(e->flag & 2)
+ e2 = recvp(w->cevent);
+ if(e->flag & 8){
+ ea = recvp(w->cevent);
+ na = ea->nb;
+ recvp(w->cevent);
+ }else
+ na = 0;
+
+ needfree = 0;
+ s = e->b;
+ if(e->nb==0 && (e->flag&2)){
+ s = e2->b;
+ e->q0 = e2->q0;
+ e->q1 = e2->q1;
+ e->nb = e2->nb;
+ }
+ if(e->nb==0 && e->q0<e->q1){
+ /* fetch data from window */
+ s = emalloc((e->q1-e->q0)*UTFmax+2);
+ n = winread(w, e->q0, e->q1, s);
+ s[n] = '\0';
+ needfree = 1;
+ }else
+ if(na){
+ t = emalloc(strlen(s)+1+na+2);
+ sprint(t, "%s %s", s, ea->b);
+ if(needfree)
+ free(s);
+ s = t;
+ needfree = 1;
+ }
+
+ /* if it's a known command, do it */
+ /* if it's a long message, it can't be for us anyway */
+ if(!command(w, s) && s[0]!='\0'){ /* send it as typed text */
+ /* if it's a built-in from the tag, send it back */
+ if(e->flag & 1)
+ fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
+ else{ /* send text to main window */
+ len = strlen(s);
+ if(len>0 && s[len-1]!='\n' && s[len-1]!='\004'){
+ if(!needfree){
+ /* if(needfree), we left room for a newline before */
+ t = emalloc(len+2);
+ strcpy(t, s);
+ s = t;
+ needfree = 1;
+ }
+ s[len++] = '\n';
+ s[len] = '\0';
+ }
+ sendit(s);
+ }
+ }
+ if(needfree)
+ free(s);
+}
+
+int
+hasboundary(Rune *r, int nr)
+{
+ int i;
+
+ for(i=0; i<nr; i++)
+ if(r[i]=='\n' || r[i]=='\004')
+ return 1;
+ return 0;
+}
+
+void
+mainctl(void *v)
+{
+ Window *w;
+ Event *e;
+ int delta, pendingS, pendingK;
+ ulong hostpt, endpt;
+ char tmp[32];
+
+ w = v;
+ proccreate(wineventproc, w, STACK);
+
+ hostpt = 0;
+ endpt = 0;
+ winsetaddr(w, "0", 0);
+ pendingS = 0;
+ pendingK = 0;
+ for(;;){
+ if(debug)
+ fprint(2, "input range %lud-%lud\n", hostpt, endpt);
+ e = recvp(w->cevent);
+ if(debug)
+ fprint(2, "msg: %C %C %d %d %d %d %q\n",
+ e->c1 ? e->c1 : ' ', e->c2 ? e->c2 : ' ', e->q0, e->q1, e->flag, e->nb, e->b);
+ switch(e->c1){
+ default:
+ Unknown:
+ fprint(2, "unknown message %c%c\n", e->c1, e->c2);
+ break;
+
+ case 'C': /* input needed for /dev/cons */
+ if(pendingS)
+ pendingK = 1;
+ else
+ hostpt += sendinput(w, hostpt, &endpt);
+ break;
+
+ case 'S': /* output to stdout */
+ sprint(tmp, "#%lud", hostpt);
+ winsetaddr(w, tmp, 0);
+ write(w->data, e->b, e->nb);
+ pendingS += e->nr;
+ break;
+
+ case 'E': /* write to tag or body; body happens due to sendit */
+ delta = e->q1-e->q0;
+ if(e->c2=='I'){
+ endpt += delta;
+ if(e->q0 < hostpt)
+ hostpt += delta;
+ else
+ hostpt += sendinput(w, hostpt, &endpt);
+ break;
+ }
+ if(!islower(e->c2))
+ fprint(2, "win msg: %C %C %d %d %d %d %q\n",
+ e->c1, e->c2, e->q0, e->q1, e->flag, e->nb, e->b);
+ break;
+
+ case 'F': /* generated by our actions (specifically case 'S' above) */
+ delta = e->q1-e->q0;
+ if(e->c2=='D'){
+ /* we know about the delete by _sendinput */
+ break;
+ }
+ if(e->c2=='I'){
+ pendingS -= e->q1 - e->q0;
+ if(pendingS < 0)
+ fprint(2, "win: pendingS = %d\n", pendingS);
+ if(e->q0 != hostpt)
+ fprint(2, "win: insert at %d expected %lud\n", e->q0, hostpt);
+ endpt += delta;
+ hostpt += delta;
+ sendp(writechan, nil);
+ if(pendingS == 0 && pendingK){
+ pendingK = 0;
+ hostpt += sendinput(w, hostpt, &endpt);
+ }
+ break;
+ }
+ if(!islower(e->c2))
+ fprint(2, "win msg: %C %C %d %d %d %d %q\n",
+ e->c1, e->c2, e->q0, e->q1, e->flag, e->nb, e->b);
+ break;
+
+ case 'K':
+ delta = e->q1-e->q0;
+ switch(e->c2){
+ case 'D':
+ endpt -= delta;
+ if(e->q1 < hostpt)
+ hostpt -= delta;
+ else if(e->q0 < hostpt)
+ hostpt = e->q0;
+ break;
+ case 'I':
+ delta = e->q1 - e->q0;
+ endpt += delta;
+ if(endpt < e->q1) /* just in case */
+ endpt = e->q1;
+ if(e->q0 < hostpt)
+ hostpt += delta;
+ if(e->nr>0 && e->r[e->nr-1]==0x7F){
+ write(notepg, "interrupt", 9);
+ hostpt = endpt;
+ break;
+ }
+ if(e->q0 >= hostpt
+ && hasboundary(e->r, e->nr)){
+ /*
+ * If we are between the S message (which
+ * we processed by inserting text in the
+ * window) and the F message notifying us
+ * that the text has been inserted, then our
+ * impression of the hostpt and acme's
+ * may be different. This could be seen if you
+ * hit enter a bunch of times in a con
+ * session. To work around the unreliability,
+ * only send input if we don't have an S pending.
+ * The same race occurs between when a character
+ * is typed and when we get notice of it, but
+ * since characters tend to be typed at the end
+ * of the buffer, we don't run into it. There's
+ * no workaround possible for this typing race,
+ * since we can't tell when the user has typed
+ * something but we just haven't been notified.
+ */
+ if(pendingS)
+ pendingK = 1;
+ else
+ hostpt += sendinput(w, hostpt, &endpt);
+ }
+ break;
+ }
+ break;
+
+ case 'M': /* mouse */
+ delta = e->q1-e->q0;
+ switch(e->c2){
+ case 'x':
+ case 'X':
+ execevent(w, e, command);
+ break;
+
+ case 'l': /* reflect all searches back to acme */
+ case 'L':
+ if(e->flag & 2)
+ recvp(w->cevent);
+ winwriteevent(w, e);
+ break;
+
+ case 'I':
+ endpt += delta;
+ if(e->q0 < hostpt)
+ hostpt += delta;
+ else
+ hostpt += sendinput(w, hostpt, &endpt);
+ break;
+
+ case 'D':
+ endpt -= delta;
+ if(e->q1 < hostpt)
+ hostpt -= delta;
+ else if(e->q0 < hostpt)
+ hostpt = e->q0;
+ break;
+ case 'd': /* modify away; we don't care */
+ case 'i':
+ break;
+
+ default:
+ goto Unknown;
+ }
+ }
+ }
+}
+
+enum
+{
+ NARGS = 100,
+ NARGCHAR = 8*1024,
+ EXECSTACK = STACK+(NARGS+1)*sizeof(char*)+NARGCHAR
+};
+
+struct Exec
+{
+ char **argv;
+ Channel *cpid;
+};
+
+int
+lookinbin(char *s)
+{
+ if(s[0] == '/')
+ return 0;
+ if(s[0]=='.' && s[1]=='/')
+ return 0;
+ if(s[0]=='.' && s[1]=='.' && s[2]=='/')
+ return 0;
+ return 1;
+}
+
+/* adapted from mail. not entirely free of details from that environment */
+void
+execproc(void *v)
+{
+ struct Exec *e;
+ char *cmd, **av;
+ Channel *cpid;
+
+ e = v;
+ rfork(RFCFDG|RFNOTEG);
+ av = e->argv;
+ close(0);
+ open("/dev/cons", OREAD);
+ close(1);
+ open("/dev/cons", OWRITE);
+ dup(1, 2);
+ cpid = e->cpid;
+ free(e);
+ procexec(cpid, av[0], av);
+ if(lookinbin(av[0])){
+ cmd = estrstrdup("/bin/", av[0]);
+ procexec(cpid, cmd, av);
+ }
+ error("can't exec %s: %r", av[0]);
+}
+
+void
+startcmd(char *argv[], int *notepg)
+{
+ struct Exec *e;
+ Channel *cpid;
+ char buf[64];
+ int pid;
+
+ e = emalloc(sizeof(struct Exec));
+ e->argv = argv;
+ cpid = chancreate(sizeof(ulong), 0);
+ e->cpid = cpid;
+ sprint(buf, "/mnt/wsys/%d", win->id);
+ bind(buf, "/dev/acme", MREPL);
+ proccreate(execproc, e, EXECSTACK);
+ do
+ pid = recvul(cpid);
+ while(pid == -1);
+ sprint(buf, "/proc/%d/notepg", pid);
+ *notepg = open(buf, OWRITE);
+}
--- /dev/null
+</$objtype/mkfile
+
+TARG=win
+OFILES=\
+ fs.$O\
+ main.$O\
+ pipe.$O\
+ util.$O\
+ win.$O
+
+HFILES=dat.h
+LIB=/$objtype/lib/lib9p.a
+
+BIN=/acme/bin/$objtype
+</sys/src/cmd/mkone
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+ ${TARG:%=/acme/bin/$objtype/%}\
+
+syms:V:
+ 8c -a main.c >syms
+ 8c -aa util.c win.c >>syms
--- /dev/null
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "dat.h"
+
+typedef struct Wpid Wpid;
+struct Wpid
+{
+ int pid;
+ Window *w;
+ Wpid *next;
+};
+
+void pipectl(void*);
+
+int pipefd;
+Wpid *wpid;
+int snarffd;
+Channel *newpipechan;
+
+int
+newpipewin(int pid, char *p)
+{
+ int id;
+ Window *w;
+ Wpid *wp;
+
+ w = newwindow();
+ winname(w, p);
+ wintagwrite(w, "Send ", 5);
+ wp = emalloc(sizeof(Wpid));
+ wp->pid = pid;
+ wp->w = w;
+ wp->next = wpid; /* BUG: this happens in fsread proc (we don't use wpid, so it's okay) */
+ wpid = wp;
+ id = w->id;
+ sendp(newpipechan, w);
+ return id;
+}
+
+int
+pipecommand(Window *w, char *s)
+{
+ ulong q0, q1;
+ char tmp[32], *t;
+ int n, k;
+
+ while(*s==' ' || *s=='\t' || *s=='\n')
+ s++;
+ if(strcmp(s, "Delete")==0){
+ windel(w, 1);
+ threadexits(nil);
+ return 1;
+ }
+ if(strcmp(s, "Del")==0){
+ if(windel(w, 0))
+ threadexits(nil);
+ return 1;
+ }
+ if(strcmp(s, "Send") == 0){
+ if(w->addr < 0)
+ w->addr = winopenfile(w, "addr");
+ ctlprint(w->ctl, "addr=dot\n");
+ seek(w->addr, 0UL, 0);
+ if(read(w->addr, tmp, 2*12) == 2*12){
+ q0 = atol(tmp+0*12);
+ q1 = atol(tmp+1*12);
+ if(q0 == q1){
+ t = nil;
+ k = 0;
+ if(snarffd > 0){
+ seek(0, snarffd, 0);
+ for(;;){
+ t = realloc(t, k+8192+2);
+ if(t == nil)
+ error("alloc failed: %r\n");
+ n = read(snarffd, t+k, 8192);
+ if(n <= 0)
+ break;
+ k += n;
+ }
+ t[k] = 0;
+ }
+ }else{
+ t = emalloc((q1-q0)*UTFmax+2);
+ winread(w, q0, q1, t);
+ k = strlen(t);
+ }
+ if(t!=nil && t[0]!='\0'){
+ if(t[k-1]!='\n' && t[k-1]!='\004'){
+ t[k++] = '\n';
+ t[k] = '\0';
+ }
+ sendit(t);
+ }
+ free(t);
+ }
+ return 1;
+ }
+ return 0;
+}
+
+void
+pipectl(void *v)
+{
+ Window *w;
+ Event *e;
+
+ w = v;
+ proccreate(wineventproc, w, STACK);
+
+ windormant(w);
+ winsetaddr(w, "0", 0);
+ for(;;){
+ e = recvp(w->cevent);
+ switch(e->c1){
+ default:
+ Unknown:
+ fprint(2, "unknown message %c%c\n", e->c1, e->c2);
+ break;
+
+ case 'E': /* write to body; can't affect us */
+ break;
+
+ case 'F': /* generated by our actions; ignore */
+ break;
+
+ case 'K': /* ignore */
+ break;
+
+ case 'M':
+ switch(e->c2){
+ case 'x':
+ case 'X':
+ execevent(w, e, pipecommand);
+ break;
+
+ case 'l': /* reflect all searches back to acme */
+ case 'L':
+ if(e->flag & 2)
+ recvp(w->cevent);
+ winwriteevent(w, e);
+ break;
+
+ case 'I': /* modify away; we don't care */
+ case 'i':
+ case 'D':
+ case 'd':
+ break;
+
+ default:
+ goto Unknown;
+ }
+ }
+ }
+}
+
+void
+newpipethread(void*)
+{
+ Window *w;
+
+ while(w = recvp(newpipechan))
+ threadcreate(pipectl, w, STACK);
+}
+
+void
+startpipe(void)
+{
+ newpipechan = chancreate(sizeof(Window*), 0);
+ threadcreate(newpipethread, nil, STACK);
+ snarffd = open("/dev/snarf", OREAD|OCEXEC);
+}
--- /dev/null
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include "dat.h"
+
+void*
+emalloc(uint n)
+{
+ void *p;
+
+ p = malloc(n);
+ if(p == nil)
+ error("can't malloc: %r");
+ memset(p, 0, n);
+ return p;
+}
+
+char*
+estrdup(char *s)
+{
+ char *t;
+
+ t = emalloc(strlen(s)+1);
+ strcpy(t, s);
+ return t;
+}
+
+char*
+estrstrdup(char *s, char *t)
+{
+ char *u;
+
+ u = emalloc(strlen(s)+strlen(t)+1);
+ sprint(u, "%s%s", s, t);
+ return u;
+}
+
+char*
+eappend(char *s, char *sep, char *t)
+{
+ char *u;
+
+ if(t == nil)
+ u = estrstrdup(s, sep);
+ else{
+ u = emalloc(strlen(s)+strlen(sep)+strlen(t)+1);
+ sprint(u, "%s%s%s", s, sep, t);
+ }
+ free(s);
+ return u;
+}
+
+char*
+egrow(char *s, char *sep, char *t)
+{
+ s = eappend(s, sep, t);
+ free(t);
+ return s;
+}
+
+void
+error(char *fmt, ...)
+{
+ Fmt f;
+ char buf[64];
+ va_list arg;
+
+ fmtfdinit(&f, 2, buf, sizeof buf);
+ fmtprint(&f, "win: ");
+ va_start(arg, fmt);
+ fmtvprint(&f, fmt, arg);
+ va_end(arg);
+ fmtprint(&f, "\n");
+ fmtfdflush(&f);
+ threadexitsall(fmt);
+}
+
+void
+ctlprint(int fd, char *fmt, ...)
+{
+ int n;
+ va_list arg;
+
+ va_start(arg, fmt);
+ n = vfprint(fd, fmt, arg);
+ va_end(arg);
+ if(n <= 0)
+ error("control file write error: %r");
+}
--- /dev/null
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include "dat.h"
+
+Window*
+newwindow(void)
+{
+ char buf[12];
+ Window *w;
+
+ w = emalloc(sizeof(Window));
+ w->ctl = open("/mnt/wsys/new/ctl", ORDWR|OCEXEC);
+ if(w->ctl<0 || read(w->ctl, buf, 12)!=12)
+ error("can't open window ctl file: %r");
+ ctlprint(w->ctl, "noscroll\n");
+ w->id = atoi(buf);
+ w->event = winopenfile(w, "event");
+ w->addr = winopenfile(w, "addr");
+ w->body = winopenfile(w, "body");
+ w->data = winopenfile(w, "data");
+ w->cevent = chancreate(sizeof(Event*), 0);
+ return w;
+}
+
+void
+winsetdump(Window *w, char *dir, char *cmd)
+{
+ if(dir != nil)
+ ctlprint(w->ctl, "dumpdir %s\n", dir);
+ if(cmd != nil)
+ ctlprint(w->ctl, "dump %s\n", cmd);
+}
+
+void
+wineventproc(void *v)
+{
+ Window *w;
+ int i;
+
+ w = v;
+ for(i=0; ; i++){
+ if(i >= NEVENT)
+ i = 0;
+ wingetevent(w, &w->e[i]);
+ sendp(w->cevent, &w->e[i]);
+ }
+}
+
+int
+winopenfile(Window *w, char *f)
+{
+ char buf[64];
+ int fd;
+
+ sprint(buf, "/mnt/wsys/%d/%s", w->id, f);
+ fd = open(buf, ORDWR|OCEXEC);
+ if(fd < 0)
+ error("can't open window file %s: %r", f);
+ return fd;
+}
+
+void
+wintagwrite(Window *w, char *s, int n)
+{
+ int fd;
+
+ fd = winopenfile(w, "tag");
+ if(write(fd, s, n) != n)
+ error("tag write: %r");
+ close(fd);
+}
+
+void
+winname(Window *w, char *s)
+{
+ ctlprint(w->ctl, "name %s\n", s);
+}
+
+int
+wingetec(Window *w)
+{
+ if(w->nbuf == 0){
+ w->nbuf = read(w->event, w->buf, sizeof w->buf);
+ if(w->nbuf <= 0){
+ /* probably because window has exited, and only called by wineventproc, so just shut down */
+ threadexits(nil);
+ }
+ w->bufp = w->buf;
+ }
+ w->nbuf--;
+ return *w->bufp++;
+}
+
+int
+wingeten(Window *w)
+{
+ int n, c;
+
+ n = 0;
+ while('0'<=(c=wingetec(w)) && c<='9')
+ n = n*10+(c-'0');
+ if(c != ' ')
+ error("event number syntax");
+ return n;
+}
+
+int
+wingeter(Window *w, char *buf, int *nb)
+{
+ Rune r;
+ int n;
+
+ r = wingetec(w);
+ buf[0] = r;
+ n = 1;
+ if(r >= Runeself) {
+ while(!fullrune(buf, n))
+ buf[n++] = wingetec(w);
+ chartorune(&r, buf);
+ }
+ *nb = n;
+ return r;
+}
+
+void
+wingetevent(Window *w, Event *e)
+{
+ int i, nb;
+
+ e->c1 = wingetec(w);
+ e->c2 = wingetec(w);
+ e->q0 = wingeten(w);
+ e->q1 = wingeten(w);
+ e->flag = wingeten(w);
+ e->nr = wingeten(w);
+ if(e->nr > EVENTSIZE)
+ error("event string too long");
+ e->nb = 0;
+ for(i=0; i<e->nr; i++){
+ e->r[i] = wingeter(w, e->b+e->nb, &nb);
+ e->nb += nb;
+ }
+ e->r[e->nr] = 0;
+ e->b[e->nb] = 0;
+ if(wingetec(w) != '\n')
+ error("event syntax error");
+}
+
+void
+winwriteevent(Window *w, Event *e)
+{
+ fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
+}
+
+static int
+nrunes(char *s, int nb)
+{
+ int i, n;
+ Rune r;
+
+ n = 0;
+ for(i=0; i<nb; n++)
+ i += chartorune(&r, s+i);
+ return n;
+}
+
+int
+winread(Window *w, uint q0, uint q1, char *data)
+{
+ int m, n, nr, nb;
+ char buf[256];
+
+ if(w->addr < 0)
+ w->addr = winopenfile(w, "addr");
+ if(w->data < 0)
+ w->data = winopenfile(w, "data");
+ m = q0;
+ nb = 0;
+ while(m < q1){
+ n = sprint(buf, "#%d", m);
+ if(write(w->addr, buf, n) != n)
+ error("error writing addr: %r");
+ n = read(w->data, buf, sizeof buf);
+ if(n < 0)
+ error("reading data: %r");
+ nr = nrunes(buf, n);
+ while(m+nr >q1){
+ do; while(n>0 && (buf[--n]&0xC0)==0x80);
+ --nr;
+ }
+ if(n == 0)
+ break;
+ memmove(data, buf, n);
+ nb += n;
+ data += n;
+ *data = 0;
+ m += nr;
+ }
+ return nb;
+}
+
+void
+windormant(Window *w)
+{
+ if(w->addr >= 0){
+ close(w->addr);
+ w->addr = -1;
+ }
+ if(w->body >= 0){
+ close(w->body);
+ w->body = -1;
+ }
+ if(w->data >= 0){
+ close(w->data);
+ w->data = -1;
+ }
+}
+
+int
+windel(Window *w, int sure)
+{
+ if(sure)
+ write(w->ctl, "delete\n", 7);
+ else if(write(w->ctl, "del\n", 4) != 4)
+ return 0;
+ /* event proc will die due to read error from event file */
+ windormant(w);
+ close(w->ctl);
+ w->ctl = -1;
+ close(w->event);
+ w->event = -1;
+ return 1;
+}
+
+void
+winclean(Window *w)
+{
+ ctlprint(w->ctl, "clean\n");
+}
+
+int
+winsetaddr(Window *w, char *addr, int errok)
+{
+ if(w->addr < 0)
+ w->addr = winopenfile(w, "addr");
+ if(write(w->addr, addr, strlen(addr)) < 0){
+ if(!errok)
+ error("error writing addr(%s): %r", addr);
+ return 0;
+ }
+ return 1;
+}
+
+int
+winselect(Window *w, char *addr, int errok)
+{
+ if(winsetaddr(w, addr, errok)){
+ ctlprint(w->ctl, "dot=addr\n");
+ return 1;
+ }
+ return 0;
+}
--- /dev/null
+#!/bin/rc
+
+sed 's/^ //' $*
--- /dev/null
+#!/bin/rc -e
+
+id=`{mkwnew $*}
+cat >/mnt/acme/$id/body
+echo clean >/mnt/acme/$id/ctl
--- /dev/null
+Mail stored
+plumb /mail/box/$user/names
+mail -'x' someaddress
+mkbox /mail/box/$user/new_box
--- /dev/null
+#!/bin/rc
+
+for(i){
+ if(! test -e $i){
+ if(cp /dev/null $i){
+ chmod 600 $i
+ chmod +al $i
+ }
+ }
+ if not echo $i already exists
+}
--- /dev/null
+The Acme Mail program uses upas/fs to parse the mail box, and then
+presents a file-browser-like user interface to reading and sending
+messages. The Mail window presents each numbered message like the
+contents of a directory presented one per line. If a message has a
+Subject: line, that is shown indented on the following line.
+Multipart MIME-encoded messages are presented in the obvious
+hierarchical format.
+
+Mail uses upas/fs to access the mail box. By default it reads "mbox",
+the standard user mail box. If Mail is given an argument, it is
+passed to upas/fs as the name of the mail box (or upas/fs directory)
+to open.
+
+Although Mail works if the plumber is not running, it's designed to be
+run with plumbing enabled and many of its features work best if it is.
+
+The mailbox window has a few commands: Put writes back the mailbox;
+Mail creates a new window in which to compose a message; and Delmesg
+deletes messages by number. The number may be given as argument or
+indicated by selecting the header line in the mailbox window.
+(Delmesg does not expand null selections, in the interest of safety.)
+
+Clicking the right button on a message number opens it; clicking on
+any of the subparts of a message opens that (and also opens the
+message itself). Each message window has a few commands in the tag
+with obvious names: Reply, Delmsg, etc. "Reply" replies to the single
+sender of the message, "Reply all" or "Replyall" replies to everyone
+in the From:, To:, and CC: lines.
+
+Message parts with recognized MIME types such as image/jpeg are sent
+to the plumber for further dispatch. Acme Mail also listens to
+messages on the seemail and showmail plumbing ports, to report the
+arrival of new messages (highlighting the entry; right-click on the
+entry to open the message) and open them if you right-click on the
+face in the faces window.
+
+When composing a mail message or replying to a message, the first line
+of the text is a list of recipients of the message. To:, and CC:, and BCC:
+lines are interpreted in the usual way. Two other header lines are
+special to Acme Mail:
+ Include: file places a copy of file in the message as an
+ inline MIME attachment.
+ Attach: file places a copy of file in the message as a regular
+ MIME attachment.
+
+Acme Mail uses these conventions when replying to messages,
+constructing headers for the default behavior. You may edit these to
+change behavior. Most important, when replying to a message Mail will
+always Include: the original message; delete that line if you don't
+want to include it.
+
+If the mailbox
+ /mail/box/$user/outgoing
+exists, Acme Mail will save your a copy of your outgoing messages
+there. Attachments are described in the copy but not included.
+
+The -m mntpoint flag specifies a different mount point for /upas/fs.
--- /dev/null
+typedef struct Event Event;
+typedef struct Exec Exec;
+typedef struct Message Message;
+typedef struct Window Window;
+
+enum
+{
+ STACK = 8192,
+ EVENTSIZE = 256,
+ NEVENT = 5,
+};
+
+struct Event
+{
+ int c1;
+ int c2;
+ int q0;
+ int q1;
+ int flag;
+ int nb;
+ int nr;
+ char b[EVENTSIZE*UTFmax+1];
+ Rune r[EVENTSIZE+1];
+};
+
+struct Window
+{
+ /* file descriptors */
+ int ctl;
+ int event;
+ int addr;
+ int data;
+ Biobuf *body;
+
+ /* event input */
+ char buf[512];
+ char *bufp;
+ int nbuf;
+ Event e[NEVENT];
+
+ int id;
+ int open;
+ Channel *cevent;
+};
+
+struct Message
+{
+ Window *w;
+ int ctlfd;
+ char *name;
+ char *replyname;
+ uchar opened;
+ uchar dirty;
+ uchar isreply;
+ uchar deleted;
+ uchar writebackdel;
+ uchar tagposted;
+ uchar recursed;
+ uchar level;
+
+ /* header info */
+ char *fromcolon; /* from header file; all rest are from info file */
+ char *from;
+ char *to;
+ char *cc;
+ char *replyto;
+ char *date;
+ char *subject;
+ char *type;
+ char *disposition;
+ char *filename;
+ char *digest;
+
+ Message *next; /* next in this mailbox */
+ Message *prev; /* prev in this mailbox */
+ Message *head; /* first subpart */
+ Message *tail; /* last subpart */
+};
+
+enum
+{
+ NARGS = 100,
+ NARGCHAR = 8*1024,
+ EXECSTACK = STACK+(NARGS+1)*sizeof(char*)+NARGCHAR
+};
+
+struct Exec
+{
+ char *prog;
+ char **argv;
+ int p[2]; /* p[1] is write to program; p[0] set to prog fd 0*/
+ int q[2]; /* q[0] is read from program; q[1] set to prog fd 1 */
+ Channel *sync;
+};
+
+extern Window* newwindow(void);
+extern int winopenfile(Window*, char*);
+extern void winopenbody(Window*, int);
+extern void winclosebody(Window*);
+extern void wintagwrite(Window*, char*, int);
+extern void winname(Window*, char*);
+extern void winwriteevent(Window*, Event*);
+extern void winread(Window*, uint, uint, char*);
+extern int windel(Window*, int);
+extern void wingetevent(Window*, Event*);
+extern void wineventproc(void*);
+extern void winwritebody(Window*, char*, int);
+extern void winclean(Window*);
+extern int winselect(Window*, char*, int);
+extern char* winselection(Window*);
+extern int winsetaddr(Window*, char*, int);
+extern char* winreadbody(Window*, int*);
+extern void windormant(Window*);
+extern void winsetdump(Window*, char*, char*);
+
+extern void readmbox(Message*, char*, char*);
+extern void rewritembox(Window*, Message*);
+
+extern void mkreply(Message*, char*, char*, Plumbattr*, char*);
+extern void delreply(Message*);
+
+extern int mesgadd(Message*, char*, Dir*, char*);
+extern void mesgmenu(Window*, Message*);
+extern void mesgmenunew(Window*, Message*);
+extern int mesgopen(Message*, char*, char*, Message*, int, char*);
+extern void mesgctl(void*);
+extern void mesgsend(Message*);
+extern void mesgdel(Message*, Message*);
+extern void mesgmenudel(Window*, Message*, Message*);
+extern void mesgmenumark(Window*, char*, char*);
+extern void mesgmenumarkdel(Window*, Message*, Message*, int);
+extern Message* mesglookup(Message*, char*, char*);
+extern Message* mesglookupfile(Message*, char*, char*);
+extern void mesgfreeparts(Message*);
+
+extern char* readfile(char*, char*, int*);
+extern char* readbody(char*, char*, int*);
+extern void ctlprint(int, char*, ...);
+extern void* emalloc(uint);
+extern void* erealloc(void*, uint);
+extern char* estrdup(char*);
+extern char* estrstrdup(char*, char*);
+extern char* egrow(char*, char*, char*);
+extern char* eappend(char*, char*, char*);
+extern void error(char*, ...);
+extern int tokenizec(char*, char**, int, char*);
+extern void execproc(void*);
+
+#pragma varargck argpos error 1
+#pragma varargck argpos ctlprint 2
+
+extern Window *wbox;
+extern Message mbox;
+extern Message replies;
+extern char *fsname;
+extern int plumbsendfd;
+extern int plumbseemailfd;
+extern char *home;
+extern char *outgoing;
+extern char *mailboxdir;
+extern char *user;
+extern char deleted[];
+extern int wctlfd;
+extern int shortmenu;
--- /dev/null
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <ctype.h>
+#include <plumb.h>
+#include "dat.h"
+
+
+char*
+formathtml(char *body, int *np)
+{
+ int i, j, p[2], q[2];
+ Exec *e;
+ char buf[1024];
+ Channel *sync;
+
+ e = emalloc(sizeof(struct Exec));
+ if(pipe(p) < 0 || pipe(q) < 0)
+ error("can't create pipe: %r");
+
+ e->p[0] = p[0];
+ e->p[1] = p[1];
+ e->q[0] = q[0];
+ e->q[1] = q[1];
+ e->argv = emalloc(3*sizeof(char*));
+ e->argv[0] = estrdup("htmlfmt");
+ e->argv[1] = estrdup("-cutf-8");
+ e->argv[2] = nil;
+ e->prog = "/bin/htmlfmt";
+ sync = chancreate(sizeof(int), 0);
+ e->sync = sync;
+ proccreate(execproc, e, EXECSTACK);
+ recvul(sync);
+ close(p[0]);
+ close(q[1]);
+
+ if((i=write(p[1], body, *np)) != *np){
+ fprint(2, "Mail: warning: htmlfmt failed: wrote %d of %d: %r\n", i, *np);
+ close(p[1]);
+ close(q[0]);
+ return body;
+ }
+ close(p[1]);
+
+ free(body);
+ body = nil;
+ i = 0;
+ for(;;){
+ j = read(q[0], buf, sizeof buf);
+ if(j <= 0)
+ break;
+ body = realloc(body, i+j+1);
+ if(body == nil)
+ error("realloc failed: %r");
+ memmove(body+i, buf, j);
+ i += j;
+ body[i] = '\0';
+ }
+ close(q[0]);
+
+ *np = i;
+ return body;
+}
+
+char*
+readbody(char *type, char *dir, int *np)
+{
+ char *body;
+
+ body = readfile(dir, "body", np);
+ if(body != nil && strcmp(type, "text/html") == 0)
+ return formathtml(body, np);
+ return body;
+}
--- /dev/null
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <plumb.h>
+#include <ctype.h>
+#include "dat.h"
+
+char *maildir = "/mail/fs/"; /* mountpoint of mail file system */
+char *mailtermdir = "/mnt/term/mail/fs/"; /* alternate mountpoint */
+char *mboxname = "mbox"; /* mailboxdir/mboxname is mail spool file */
+char *mailboxdir = nil; /* nil == /mail/box/$user */
+char *fsname; /* filesystem for mailboxdir/mboxname is at maildir/fsname */
+char *user;
+char *outgoing;
+
+Window *wbox;
+Message mbox;
+Message replies;
+char *home;
+int plumbsendfd;
+int plumbseemailfd;
+int plumbshowmailfd;
+int plumbsendmailfd;
+Channel *cplumb;
+Channel *cplumbshow;
+Channel *cplumbsend;
+int wctlfd;
+void mainctl(void*);
+void plumbproc(void*);
+void plumbshowproc(void*);
+void plumbsendproc(void*);
+void plumbthread(void);
+void plumbshowthread(void*);
+void plumbsendthread(void*);
+
+int shortmenu;
+
+void
+usage(void)
+{
+ fprint(2, "usage: Mail [-sS] [-o outgoing] [mailboxname [directoryname]]\n");
+ threadexitsall("usage");
+}
+
+void
+removeupasfs(void)
+{
+ char buf[256];
+
+ if(strcmp(mboxname, "mbox") == 0)
+ return;
+ snprint(buf, sizeof buf, "close %s", mboxname);
+ write(mbox.ctlfd, buf, strlen(buf));
+}
+
+int
+ismaildir(char *s)
+{
+ char buf[256];
+ Dir *d;
+ int ret;
+
+ snprint(buf, sizeof buf, "%s%s", maildir, s);
+ d = dirstat(buf);
+ if(d == nil)
+ return 0;
+ ret = d->qid.type & QTDIR;
+ free(d);
+ return ret;
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ char *s, *name;
+ char err[ERRMAX], *cmd;
+ int i, newdir;
+ Fmt fmt;
+
+ doquote = needsrcquote;
+ quotefmtinstall();
+
+ /* open these early so we won't miss notification of new mail messages while we read mbox */
+ plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
+ plumbseemailfd = plumbopen("seemail", OREAD|OCEXEC);
+ plumbshowmailfd = plumbopen("showmail", OREAD|OCEXEC);
+
+ shortmenu = 0;
+ ARGBEGIN{
+ case 's':
+ shortmenu = 1;
+ break;
+ case 'S':
+ shortmenu = 2;
+ break;
+ case 'o':
+ outgoing = EARGF(usage());
+ break;
+ case 'm':
+ smprint(maildir, "%s/", EARGF(usage()));
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ name = "mbox";
+
+ /* bind the terminal /mail/fs directory over the local one */
+ if(access(maildir, 0)<0 && access(mailtermdir, 0)==0)
+ bind(mailtermdir, maildir, MAFTER);
+
+ newdir = 1;
+ if(argc > 0){
+ i = strlen(argv[0]);
+ if(argc>2 || i==0)
+ usage();
+ /* see if the name is that of an existing /mail/fs directory */
+ if(argc==1 && strchr(argv[0], '/')==0 && ismaildir(argv[0])){
+ name = argv[0];
+ mboxname = eappend(estrdup(maildir), "", name);
+ newdir = 0;
+ }else{
+ if(argv[0][i-1] == '/')
+ argv[0][i-1] = '\0';
+ s = strrchr(argv[0], '/');
+ if(s == nil)
+ mboxname = estrdup(argv[0]);
+ else{
+ *s++ = '\0';
+ if(*s == '\0')
+ usage();
+ mailboxdir = argv[0];
+ mboxname = estrdup(s);
+ }
+ if(argc > 1)
+ name = argv[1];
+ else
+ name = mboxname;
+ }
+ }
+
+ user = getenv("user");
+ if(user == nil)
+ user = "none";
+ if(mailboxdir == nil)
+ mailboxdir = estrstrdup("/mail/box/", user);
+ if(outgoing == nil)
+ outgoing = estrstrdup(mailboxdir, "/outgoing");
+
+ s = estrstrdup(maildir, "ctl");
+ mbox.ctlfd = open(s, ORDWR|OCEXEC);
+ if(mbox.ctlfd < 0)
+ error("can't open %s: %r", s);
+
+ fsname = estrdup(name);
+ if(newdir && argc > 0){
+ s = emalloc(5+strlen(mailboxdir)+strlen(mboxname)+strlen(name)+10+1);
+ for(i=0; i<10; i++){
+ sprint(s, "open %s/%s %s", mailboxdir, mboxname, fsname);
+ if(write(mbox.ctlfd, s, strlen(s)) >= 0)
+ break;
+ err[0] = '\0';
+ errstr(err, sizeof err);
+ if(strstr(err, "mbox name in use") == nil)
+ error("can't create directory %s for mail: %s", name, err);
+ free(fsname);
+ fsname = emalloc(strlen(name)+10);
+ sprint(fsname, "%s-%d", name, i);
+ }
+ if(i == 10)
+ error("can't open %s/%s: %r", mailboxdir, mboxname);
+ free(s);
+ }
+
+ s = estrstrdup(fsname, "/");
+ mbox.name = estrstrdup(maildir, s);
+ mbox.level= 0;
+ readmbox(&mbox, maildir, s);
+ home = getenv("home");
+ if(home == nil)
+ home = "/";
+
+ wbox = newwindow();
+ winname(wbox, mbox.name);
+ wintagwrite(wbox, "Put Mail Delmesg ", 3+1+4+1+7+1);
+ threadcreate(mainctl, wbox, STACK);
+
+ fmtstrinit(&fmt);
+ fmtprint(&fmt, "Mail");
+ if(shortmenu)
+ fmtprint(&fmt, " -%c", "sS"[shortmenu-1]);
+ if(outgoing)
+ fmtprint(&fmt, " -o %s", outgoing);
+ fmtprint(&fmt, " %s", name);
+ cmd = fmtstrflush(&fmt);
+ if(cmd == nil)
+ sysfatal("out of memory");
+ winsetdump(wbox, "/acme/mail", cmd);
+ mbox.w = wbox;
+
+ mesgmenu(wbox, &mbox);
+ winclean(wbox);
+
+ wctlfd = open("/dev/wctl", OWRITE|OCEXEC); /* for acme window */
+ cplumb = chancreate(sizeof(Plumbmsg*), 0);
+ cplumbshow = chancreate(sizeof(Plumbmsg*), 0);
+ if(strcmp(name, "mbox") == 0){
+ /*
+ * Avoid creating multiple windows to send mail by only accepting
+ * sendmail plumb messages if we're reading the main mailbox.
+ */
+ plumbsendmailfd = plumbopen("sendmail", OREAD|OCEXEC);
+ cplumbsend = chancreate(sizeof(Plumbmsg*), 0);
+ proccreate(plumbsendproc, nil, STACK);
+ threadcreate(plumbsendthread, nil, STACK);
+ }
+ /* start plumb reader as separate proc ... */
+ proccreate(plumbproc, nil, STACK);
+ proccreate(plumbshowproc, nil, STACK);
+ threadcreate(plumbshowthread, nil, STACK);
+ /* ... and use this thread to read the messages */
+ plumbthread();
+}
+
+void
+plumbproc(void*)
+{
+ Plumbmsg *m;
+
+ threadsetname("plumbproc");
+ for(;;){
+ m = plumbrecv(plumbseemailfd);
+ sendp(cplumb, m);
+ if(m == nil)
+ threadexits(nil);
+ }
+}
+
+void
+plumbshowproc(void*)
+{
+ Plumbmsg *m;
+
+ threadsetname("plumbshowproc");
+ for(;;){
+ m = plumbrecv(plumbshowmailfd);
+ sendp(cplumbshow, m);
+ if(m == nil)
+ threadexits(nil);
+ }
+}
+
+void
+plumbsendproc(void*)
+{
+ Plumbmsg *m;
+
+ threadsetname("plumbsendproc");
+ for(;;){
+ m = plumbrecv(plumbsendmailfd);
+ sendp(cplumbsend, m);
+ if(m == nil)
+ threadexits(nil);
+ }
+}
+
+void
+newmesg(char *name, char *digest)
+{
+ Dir *d;
+
+ if(strncmp(name, mbox.name, strlen(mbox.name)) != 0)
+ return; /* message is about another mailbox */
+ if(mesglookupfile(&mbox, name, digest) != nil)
+ return;
+ d = dirstat(name);
+ if(d == nil)
+ return;
+ if(mesgadd(&mbox, mbox.name, d, digest))
+ mesgmenunew(wbox, &mbox);
+ free(d);
+}
+
+void
+showmesg(char *name, char *digest)
+{
+ char *n;
+
+ if(strncmp(name, mbox.name, strlen(mbox.name)) != 0)
+ return; /* message is about another mailbox */
+ n = estrdup(name+strlen(mbox.name));
+ if(n[strlen(n)-1] != '/')
+ n = egrow(n, "/", nil);
+ mesgopen(&mbox, mbox.name, name+strlen(mbox.name), nil, 1, digest);
+ free(n);
+}
+
+void
+delmesg(char *name, char *digest, int dodel)
+{
+ Message *m;
+
+ m = mesglookupfile(&mbox, name, digest);
+ if(m != nil){
+ mesgmenumarkdel(wbox, &mbox, m, 0);
+ if(dodel)
+ m->writebackdel = 1;
+ }
+}
+
+void
+plumbthread(void)
+{
+ Plumbmsg *m;
+ Plumbattr *a;
+ char *type, *digest;
+
+ threadsetname("plumbthread");
+ while((m = recvp(cplumb)) != nil){
+ a = m->attr;
+ digest = plumblookup(a, "digest");
+ type = plumblookup(a, "mailtype");
+ if(type == nil)
+ fprint(2, "Mail: plumb message with no mailtype attribute\n");
+ else if(strcmp(type, "new") == 0)
+ newmesg(m->data, digest);
+ else if(strcmp(type, "delete") == 0)
+ delmesg(m->data, digest, 0);
+ else
+ fprint(2, "Mail: unknown plumb attribute %s\n", type);
+ plumbfree(m);
+ }
+ threadexits(nil);
+}
+
+void
+plumbshowthread(void*)
+{
+ Plumbmsg *m;
+
+ threadsetname("plumbshowthread");
+ while((m = recvp(cplumbshow)) != nil){
+ showmesg(m->data, plumblookup(m->attr, "digest"));
+ plumbfree(m);
+ }
+ threadexits(nil);
+}
+
+void
+plumbsendthread(void*)
+{
+ Plumbmsg *m;
+
+ threadsetname("plumbsendthread");
+ while((m = recvp(cplumbsend)) != nil){
+ mkreply(nil, "Mail", m->data, m->attr, nil);
+ plumbfree(m);
+ }
+ threadexits(nil);
+}
+
+int
+mboxcommand(Window *w, char *s)
+{
+ char *args[10], **targs;
+ Message *m, *next;
+ int ok, nargs, i, j;
+ char buf[128];
+
+ nargs = tokenize(s, args, nelem(args));
+ if(nargs == 0)
+ return 0;
+ if(strcmp(args[0], "Mail") == 0){
+ if(nargs == 1)
+ mkreply(nil, "Mail", "", nil, nil);
+ else
+ mkreply(nil, "Mail", args[1], nil, nil);
+ return 1;
+ }
+ if(strcmp(s, "Del") == 0){
+ if(mbox.dirty){
+ mbox.dirty = 0;
+ fprint(2, "mail: mailbox not written\n");
+ return 1;
+ }
+ ok = 1;
+ for(m=mbox.head; m!=nil; m=next){
+ next = m->next;
+ if(m->w){
+ if(windel(m->w, 0))
+ m->w = nil;
+ else
+ ok = 0;
+ }
+ }
+ for(m=replies.head; m!=nil; m=next){
+ next = m->next;
+ if(m->w){
+ if(windel(m->w, 0))
+ m->w = nil;
+ else
+ ok = 0;
+ }
+ }
+ if(ok){
+ windel(w, 1);
+ removeupasfs();
+ threadexitsall(nil);
+ }
+ return 1;
+ }
+ if(strcmp(s, "Put") == 0){
+ rewritembox(wbox, &mbox);
+ return 1;
+ }
+ if(strcmp(s, "Delmesg") == 0){
+ if(nargs > 1){
+ for(i=1; i<nargs; i++){
+ snprint(buf, sizeof buf, "%s%s", mbox.name, args[i]);
+ delmesg(buf, nil, 1);
+ }
+ }
+ s = winselection(w);
+ if(s == nil)
+ return 1;
+ nargs = 1;
+ for(i=0; s[i]; i++)
+ if(s[i] == '\n')
+ nargs++;
+ targs = emalloc(nargs*sizeof(char*)); /* could be too many for a local array */
+ nargs = getfields(s, targs, nargs, 1, "\n");
+ for(i=0; i<nargs; i++){
+ if(!isdigit(targs[i][0]))
+ continue;
+ j = atoi(targs[i]); /* easy way to parse the number! */
+ if(j == 0)
+ continue;
+ snprint(buf, sizeof buf, "%s%d", mbox.name, j);
+ delmesg(buf, nil, 1);
+ }
+ free(s);
+ free(targs);
+ return 1;
+ }
+ return 0;
+}
+
+void
+mainctl(void *v)
+{
+ Window *w;
+ Event *e, *e2, *eq, *ea;
+ int na, nopen;
+ char *s, *t, *buf;
+
+ w = v;
+ proccreate(wineventproc, w, STACK);
+
+ for(;;){
+ e = recvp(w->cevent);
+ switch(e->c1){
+ default:
+ Unknown:
+ print("unknown message %c%c\n", e->c1, e->c2);
+ break;
+
+ case 'E': /* write to body; can't affect us */
+ break;
+
+ case 'F': /* generated by our actions; ignore */
+ break;
+
+ case 'K': /* type away; we don't care */
+ break;
+
+ case 'M':
+ switch(e->c2){
+ case 'x':
+ case 'X':
+ ea = nil;
+ e2 = nil;
+ if(e->flag & 2)
+ e2 = recvp(w->cevent);
+ if(e->flag & 8){
+ ea = recvp(w->cevent);
+ na = ea->nb;
+ recvp(w->cevent);
+ }else
+ na = 0;
+ s = e->b;
+ /* if it's a known command, do it */
+ if((e->flag&2) && e->nb==0)
+ s = e2->b;
+ if(na){
+ t = emalloc(strlen(s)+1+na+1);
+ sprint(t, "%s %s", s, ea->b);
+ s = t;
+ }
+ /* if it's a long message, it can't be for us anyway */
+ if(!mboxcommand(w, s)) /* send it back */
+ winwriteevent(w, e);
+ if(na)
+ free(s);
+ break;
+
+ case 'l':
+ case 'L':
+ buf = nil;
+ eq = e;
+ if(e->flag & 2){
+ e2 = recvp(w->cevent);
+ eq = e2;
+ }
+ s = eq->b;
+ if(eq->q1>eq->q0 && eq->nb==0){
+ buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
+ winread(w, eq->q0, eq->q1, buf);
+ s = buf;
+ }
+ nopen = 0;
+ do{
+ /* skip 'deleted' string if present' */
+ if(strncmp(s, deleted, strlen(deleted)) == 0)
+ s += strlen(deleted);
+ /* skip mail box name if present */
+ if(strncmp(s, mbox.name, strlen(mbox.name)) == 0)
+ s += strlen(mbox.name);
+ nopen += mesgopen(&mbox, mbox.name, s, nil, 0, nil);
+ while(*s!='\0' && *s++!='\n')
+ ;
+ }while(*s);
+ if(nopen == 0) /* send it back */
+ winwriteevent(w, e);
+ free(buf);
+ break;
+
+ case 'I': /* modify away; we don't care */
+ case 'D':
+ case 'd':
+ case 'i':
+ break;
+
+ default:
+ goto Unknown;
+ }
+ }
+ }
+}
+
--- /dev/null
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <ctype.h>
+#include <plumb.h>
+#include "dat.h"
+
+enum
+{
+ DIRCHUNK = 32*sizeof(Dir)
+};
+
+char regexchars[] = "\\/[].+?()*^$";
+char deleted[] = "(deleted)-";
+char deletedrx[] = "\\(deleted\\)-";
+char deletedrx01[] = "(\\(deleted\\)-)?";
+char deletedaddr[] = "-#0;/^\\(deleted\\)-/";
+
+struct{
+ char *type;
+ char *port;
+ char *suffix;
+} ports[] = {
+ "text/", "edit", ".txt",
+ /* text must be first for plumbport() */
+ "image/gif", "image", ".gif",
+ "image/jpeg", "image", ".jpg",
+ "image/jpeg", "image", ".jpeg",
+ "image/png", "image", ".png",
+ "image/tiff", "image", ".tif",
+ "application/postscript", "postscript", ".ps",
+ "application/pdf", "postscript", ".pdf",
+ "application/msword", "msword", ".doc",
+ "application/rtf", "msword", ".rtf",
+ "audio/x-wav", "wav", ".wav",
+ nil, nil
+};
+
+char *goodtypes[] = {
+ "text",
+ "text/plain",
+ "message/rfc822",
+ "text/richtext",
+ "text/tab-separated-values",
+ "application/octet-stream",
+ nil,
+};
+
+struct{
+ char *type;
+ char *ext;
+} exts[] = {
+ "image/gif", ".gif",
+ "image/jpeg", ".jpg",
+ nil, nil
+};
+
+char *okheaders[] =
+{
+ "From:",
+ "Date:",
+ "To:",
+ "CC:",
+ "Subject:",
+ nil
+};
+
+char *extraheaders[] =
+{
+ "Resent-From:",
+ "Resent-To:",
+ "Sort:",
+ nil,
+};
+
+char*
+line(char *data, char **pp)
+{
+ char *p, *q;
+
+ for(p=data; *p!='\0' && *p!='\n'; p++)
+ ;
+ if(*p == '\n')
+ *pp = p+1;
+ else
+ *pp = p;
+ q = emalloc(p-data + 1);
+ memmove(q, data, p-data);
+ return q;
+}
+
+void
+scanheaders(Message *m, char *dir)
+{
+ char *s, *t, *u, *f;
+
+ s = f = readfile(dir, "header", nil);
+ if(s != nil)
+ while(*s){
+ t = line(s, &s);
+ if(strncmp(t, "From: ", 6) == 0){
+ m->fromcolon = estrdup(t+6);
+ /* remove all quotes; they're ugly and irregular */
+ for(u=m->fromcolon; *u; u++)
+ if(*u == '"')
+ memmove(u, u+1, strlen(u));
+ }
+ if(strncmp(t, "Subject: ", 9) == 0)
+ m->subject = estrdup(t+9);
+ free(t);
+ }
+ if(m->fromcolon == nil)
+ m->fromcolon = estrdup(m->from);
+ free(f);
+}
+
+int
+loadinfo(Message *m, char *dir)
+{
+ int n;
+ char *data, *p, *s;
+
+ data = readfile(dir, "info", &n);
+ if(data == nil)
+ return 0;
+ m->from = line(data, &p);
+ scanheaders(m, dir); /* depends on m->from being set */
+ m->to = line(p, &p);
+ m->cc = line(p, &p);
+ m->replyto = line(p, &p);
+ m->date = line(p, &p);
+ s = line(p, &p);
+ if(m->subject == nil)
+ m->subject = s;
+ else
+ free(s);
+ m->type = line(p, &p);
+ m->disposition = line(p, &p);
+ m->filename = line(p, &p);
+ m->digest = line(p, &p);
+ free(data);
+ return 1;
+}
+
+int
+isnumeric(char *s)
+{
+ while(*s){
+ if(!isdigit(*s))
+ return 0;
+ s++;
+ }
+ return 1;
+}
+
+Dir*
+loaddir(char *name, int *np)
+{
+ int fd;
+ Dir *dp;
+
+ fd = open(name, OREAD);
+ if(fd < 0)
+ return nil;
+ *np = dirreadall(fd, &dp);
+ close(fd);
+ return dp;
+}
+
+void
+readmbox(Message *mbox, char *dir, char *subdir)
+{
+ char *name;
+ Dir *d, *dirp;
+ int i, n;
+
+ name = estrstrdup(dir, subdir);
+ dirp = loaddir(name, &n);
+ mbox->recursed = 1;
+ if(dirp)
+ for(i=0; i<n; i++){
+ d = &dirp[i];
+ if(isnumeric(d->name))
+ mesgadd(mbox, name, d, nil);
+ }
+ free(dirp);
+ free(name);
+}
+
+/* add message to box, in increasing numerical order */
+int
+mesgadd(Message *mbox, char *dir, Dir *d, char *digest)
+{
+ Message *m;
+ char *name;
+ int loaded;
+
+ m = emalloc(sizeof(Message));
+ m->name = estrstrdup(d->name, "/");
+ m->next = nil;
+ m->prev = mbox->tail;
+ m->level= mbox->level+1;
+ m->recursed = 0;
+ name = estrstrdup(dir, m->name);
+ loaded = loadinfo(m, name);
+ free(name);
+ /* if two upas/fs are running, we can get misled, so check digest before accepting message */
+ if(loaded==0 || (digest!=nil && m->digest!=nil && strcmp(digest, m->digest)!=0)){
+ mesgfreeparts(m);
+ free(m);
+ return 0;
+ }
+ if(mbox->tail != nil)
+ mbox->tail->next = m;
+ mbox->tail = m;
+ if(mbox->head == nil)
+ mbox->head = m;
+
+ if (m->level != 1){
+ m->recursed = 1;
+ readmbox(m, dir, m->name);
+ }
+ return 1;
+}
+
+int
+thisyear(char *year)
+{
+ static char now[10];
+ char *s;
+
+ if(now[0] == '\0'){
+ s = ctime(time(nil));
+ strcpy(now, s+24);
+ }
+ return strncmp(year, now, 4) == 0;
+}
+
+char*
+stripdate(char *as)
+{
+ int n;
+ char *s, *fld[10];
+
+ as = estrdup(as);
+ s = estrdup(as);
+ n = tokenize(s, fld, 10);
+ if(n > 5){
+ sprint(as, "%.3s ", fld[0]); /* day */
+ /* some dates have 19 Apr, some Apr 19 */
+ if(strlen(fld[1])<4 && isnumeric(fld[1]))
+ sprint(as+strlen(as), "%.3s %.3s ", fld[1], fld[2]); /* date, month */
+ else
+ sprint(as+strlen(as), "%.3s %.3s ", fld[2], fld[1]); /* date, month */
+ /* do we use time or year? depends on whether year matches this one */
+ if(thisyear(fld[5])){
+ if(strchr(fld[3], ':') != nil)
+ sprint(as+strlen(as), "%.5s ", fld[3]); /* time */
+ else if(strchr(fld[4], ':') != nil)
+ sprint(as+strlen(as), "%.5s ", fld[4]); /* time */
+ }else
+ sprint(as+strlen(as), "%.4s ", fld[5]); /* year */
+ }
+ free(s);
+ return as;
+}
+
+char*
+readfile(char *dir, char *name, int *np)
+{
+ char *file, *data;
+ int fd, len;
+ Dir *d;
+
+ if(np != nil)
+ *np = 0;
+ file = estrstrdup(dir, name);
+ fd = open(file, OREAD);
+ if(fd < 0)
+ return nil;
+ d = dirfstat(fd);
+ free(file);
+ len = 0;
+ if(d != nil)
+ len = d->length;
+ free(d);
+ data = emalloc(len+1);
+ read(fd, data, len);
+ close(fd);
+ if(np != nil)
+ *np = len;
+ return data;
+}
+
+char*
+info(Message *m, int ind, int ogf)
+{
+ char *i;
+ int j, len, lens;
+ char *p;
+ char fmt[80], s[80];
+
+ if (ogf)
+ p=m->to;
+ else
+ p=m->fromcolon;
+
+ if(ind==0 && shortmenu){
+ len = 30;
+ lens = 30;
+ if(shortmenu > 1){
+ len = 10;
+ lens = 25;
+ }
+ if(ind==0 && m->subject[0]=='\0'){
+ snprint(fmt, sizeof fmt, " %%-%d.%ds", len, len);
+ snprint(s, sizeof s, fmt, p);
+ }else{
+ snprint(fmt, sizeof fmt, " %%-%d.%ds %%-%d.%ds", len, len, lens, lens);
+ snprint(s, sizeof s, fmt, p, m->subject);
+ }
+ i = estrdup(s);
+
+ return i;
+ }
+
+ i = estrdup("");
+ i = eappend(i, "\t", p);
+ i = egrow(i, "\t", stripdate(m->date));
+ if(ind == 0){
+ if(strcmp(m->type, "text")!=0 && strncmp(m->type, "text/", 5)!=0 &&
+ strncmp(m->type, "multipart/", 10)!=0)
+ i = egrow(i, "\t(", estrstrdup(m->type, ")"));
+ }else if(strncmp(m->type, "multipart/", 10) != 0)
+ i = egrow(i, "\t(", estrstrdup(m->type, ")"));
+ if(m->subject[0] != '\0'){
+ i = eappend(i, "\n", nil);
+ for(j=0; j<ind; j++)
+ i = eappend(i, "\t", nil);
+ i = eappend(i, "\t", m->subject);
+ }
+ return i;
+}
+
+void
+mesgmenu0(Window *w, Message *mbox, char *realdir, char *dir, int ind, Biobuf *fd, int onlyone, int dotail)
+{
+ int i;
+ Message *m;
+ char *name, *tmp;
+ int ogf=0;
+
+ if(strstr(realdir, "outgoing") != nil)
+ ogf=1;
+
+ /* show mail box in reverse order, pieces in forward order */
+ if(ind > 0)
+ m = mbox->head;
+ else
+ m = mbox->tail;
+ while(m != nil){
+ for(i=0; i<ind; i++)
+ Bprint(fd, "\t");
+ if(ind != 0)
+ Bprint(fd, " ");
+ name = estrstrdup(dir, m->name);
+ tmp = info(m, ind, ogf);
+ Bprint(fd, "%s%s\n", name, tmp);
+ free(tmp);
+ if(dotail && m->tail)
+ mesgmenu0(w, m, realdir, name, ind+1, fd, 0, dotail);
+ free(name);
+ if(ind)
+ m = m->next;
+ else
+ m = m->prev;
+ if(onlyone)
+ m = nil;
+ }
+}
+
+void
+mesgmenu(Window *w, Message *mbox)
+{
+ winopenbody(w, OWRITE);
+ mesgmenu0(w, mbox, mbox->name, "", 0, w->body, 0, !shortmenu);
+ winclosebody(w);
+}
+
+/* one new message has arrived, as mbox->tail */
+void
+mesgmenunew(Window *w, Message *mbox)
+{
+ Biobuf *b;
+
+ winselect(w, "0", 0);
+ w->data = winopenfile(w, "data");
+ b = emalloc(sizeof(Biobuf));
+ Binit(b, w->data, OWRITE);
+ mesgmenu0(w, mbox, mbox->name, "", 0, b, 1, !shortmenu);
+ Bterm(b);
+ free(b);
+ if(!mbox->dirty)
+ winclean(w);
+ /* select tag line plus following indented lines, but not final newline (it's distinctive) */
+ winselect(w, "0/.*\\n((\t.*\\n)*\t.*)?/", 1);
+ close(w->addr);
+ close(w->data);
+ w->addr = -1;
+ w->data = -1;
+}
+
+char*
+name2regexp(char *prefix, char *s)
+{
+ char *buf, *p, *q;
+
+ buf = emalloc(strlen(prefix)+2*strlen(s)+50); /* leave room to append more */
+ p = buf;
+ *p++ = '0';
+ *p++ = '/';
+ *p++ = '^';
+ strcpy(p, prefix);
+ p += strlen(prefix);
+ for(q=s; *q!='\0'; q++){
+ if(strchr(regexchars, *q) != nil)
+ *p++ = '\\';
+ *p++ = *q;
+ }
+ *p++ = '/';
+ *p = '\0';
+ return buf;
+}
+
+void
+mesgmenumarkdel(Window *w, Message *mbox, Message *m, int writeback)
+{
+ char *buf;
+
+
+ if(m->deleted)
+ return;
+ m->writebackdel = writeback;
+ if(w->data < 0)
+ w->data = winopenfile(w, "data");
+ buf = name2regexp("", m->name);
+ strcat(buf, "-#0");
+ if(winselect(w, buf, 1))
+ write(w->data, deleted, 10);
+ free(buf);
+ close(w->data);
+ close(w->addr);
+ w->addr = w->data = -1;
+ mbox->dirty = 1;
+ m->deleted = 1;
+}
+
+void
+mesgmenumarkundel(Window *w, Message*, Message *m)
+{
+ char *buf;
+
+ if(m->deleted == 0)
+ return;
+ if(w->data < 0)
+ w->data = winopenfile(w, "data");
+ buf = name2regexp(deletedrx, m->name);
+ if(winselect(w, buf, 1))
+ if(winsetaddr(w, deletedaddr, 1))
+ write(w->data, "", 0);
+ free(buf);
+ close(w->data);
+ close(w->addr);
+ w->addr = w->data = -1;
+ m->deleted = 0;
+}
+
+void
+mesgmenudel(Window *w, Message *mbox, Message *m)
+{
+ char *buf;
+
+ if(w->data < 0)
+ w->data = winopenfile(w, "data");
+ buf = name2regexp(deletedrx, m->name);
+ if(winsetaddr(w, buf, 1) && winsetaddr(w, ".,./.*\\n(\t.*\\n)*/", 1))
+ write(w->data, "", 0);
+ free(buf);
+ close(w->data);
+ close(w->addr);
+ w->addr = w->data = -1;
+ mbox->dirty = 1;
+ m->deleted = 1;
+}
+
+void
+mesgmenumark(Window *w, char *which, char *mark)
+{
+ char *buf;
+
+ if(w->data < 0)
+ w->data = winopenfile(w, "data");
+ buf = name2regexp(deletedrx01, which);
+ if(winsetaddr(w, buf, 1) && winsetaddr(w, "+0-#1", 1)) /* go to end of line */
+ write(w->data, mark, strlen(mark));
+ free(buf);
+ close(w->data);
+ close(w->addr);
+ w->addr = w->data = -1;
+ if(!mbox.dirty)
+ winclean(w);
+}
+
+void
+mesgfreeparts(Message *m)
+{
+ free(m->name);
+ free(m->replyname);
+ free(m->fromcolon);
+ free(m->from);
+ free(m->to);
+ free(m->cc);
+ free(m->replyto);
+ free(m->date);
+ free(m->subject);
+ free(m->type);
+ free(m->disposition);
+ free(m->filename);
+ free(m->digest);
+}
+
+void
+mesgdel(Message *mbox, Message *m)
+{
+ Message *n, *next;
+
+ if(m->opened)
+ error("internal error: deleted message still open in mesgdel");
+ /* delete subparts */
+ for(n=m->head; n!=nil; n=next){
+ next = n->next;
+ mesgdel(m, n);
+ }
+ /* remove this message from list */
+ if(m->next)
+ m->next->prev = m->prev;
+ else
+ mbox->tail = m->prev;
+ if(m->prev)
+ m->prev->next = m->next;
+ else
+ mbox->head = m->next;
+
+ mesgfreeparts(m);
+}
+
+int
+mesgsave(Message *m, char *s)
+{
+ int ofd, n, k, ret;
+ char *t, *raw, *unixheader, *all;
+
+ t = estrstrdup(mbox.name, m->name);
+ raw = readfile(t, "raw", &n);
+ unixheader = readfile(t, "unixheader", &k);
+ if(raw==nil || unixheader==nil){
+ fprint(2, "Mail: can't read %s: %r\n", t);
+ free(t);
+ return 0;
+ }
+ free(t);
+
+ all = emalloc(n+k+1);
+ memmove(all, unixheader, k);
+ memmove(all+k, raw, n);
+ memmove(all+k+n, "\n", 1);
+ n = k+n+1;
+ free(unixheader);
+ free(raw);
+ ret = 1;
+ s = estrdup(s);
+ if(s[0] != '/')
+ s = egrow(estrdup(mailboxdir), "/", s);
+ ofd = open(s, OWRITE);
+ if(ofd < 0){
+ fprint(2, "Mail: can't open %s: %r\n", s);
+ ret = 0;
+ }else if(seek(ofd, 0LL, 2)<0 || write(ofd, all, n)!=n){
+ fprint(2, "Mail: save failed: can't write %s: %r\n", s);
+ ret = 0;
+ }
+ free(all);
+ close(ofd);
+ free(s);
+ return ret;
+}
+
+int
+mesgcommand(Message *m, char *cmd)
+{
+ char *s;
+ char *args[10];
+ int ok, ret, nargs;
+
+ s = cmd;
+ ret = 1;
+ nargs = tokenize(s, args, nelem(args));
+ if(nargs == 0)
+ return 0;
+ if(strcmp(args[0], "Post") == 0){
+ mesgsend(m);
+ goto Return;
+ }
+ if(strncmp(args[0], "Save", 4) == 0){
+ if(m->isreply)
+ goto Return;
+ s = estrdup("\t[saved");
+ if(nargs==1 || strcmp(args[1], "")==0){
+ ok = mesgsave(m, "stored");
+ }else{
+ ok = mesgsave(m, args[1]);
+ s = eappend(s, " ", args[1]);
+ }
+ if(ok){
+ s = egrow(s, "]", nil);
+ mesgmenumark(mbox.w, m->name, s);
+ }
+ free(s);
+ goto Return;
+ }
+ if(strcmp(args[0], "Reply")==0){
+ if(nargs>=2 && strcmp(args[1], "all")==0)
+ mkreply(m, "Replyall", nil, nil, nil);
+ else
+ mkreply(m, "Reply", nil, nil, nil);
+ goto Return;
+ }
+ if(strcmp(args[0], "Q") == 0){
+ s = winselection(m->w); /* will be freed by mkreply */
+ if(nargs>=3 && strcmp(args[1], "Reply")==0 && strcmp(args[2], "all")==0)
+ mkreply(m, "QReplyall", nil, nil, s);
+ else
+ mkreply(m, "QReply", nil, nil, s);
+ goto Return;
+ }
+ if(strcmp(args[0], "Del") == 0){
+ if(windel(m->w, 0)){
+ chanfree(m->w->cevent);
+ free(m->w);
+ m->w = nil;
+ if(m->isreply)
+ delreply(m);
+ else{
+ m->opened = 0;
+ m->tagposted = 0;
+ }
+ free(cmd);
+ threadexits(nil);
+ }
+ goto Return;
+ }
+ if(strcmp(args[0], "Delmesg") == 0){
+ if(!m->isreply){
+ mesgmenumarkdel(wbox, &mbox, m, 1);
+ free(cmd); /* mesgcommand might not return */
+ mesgcommand(m, estrdup("Del"));
+ return 1;
+ }
+ goto Return;
+ }
+ if(strcmp(args[0], "UnDelmesg") == 0){
+ if(!m->isreply && m->deleted)
+ mesgmenumarkundel(wbox, &mbox, m);
+ goto Return;
+ }
+// if(strcmp(args[0], "Headers") == 0){
+// m->showheaders();
+// return True;
+// }
+
+ ret = 0;
+
+ Return:
+ free(cmd);
+ return ret;
+}
+
+void
+mesgtagpost(Message *m)
+{
+ if(m->tagposted)
+ return;
+ wintagwrite(m->w, " Post", 5);
+ m->tagposted = 1;
+}
+
+/* need to expand selection more than default word */
+#pragma varargck argpos eval 2
+
+long
+eval(Window *w, char *s, ...)
+{
+ char buf[64];
+ va_list arg;
+
+ va_start(arg, s);
+ vsnprint(buf, sizeof buf, s, arg);
+ va_end(arg);
+
+ if(winsetaddr(w, buf, 1)==0)
+ return -1;
+
+ if(pread(w->addr, buf, 24, 0) != 24)
+ return -1;
+ return strtol(buf, 0, 10);
+}
+
+int
+isemail(char *s)
+{
+ int nat;
+
+ nat = 0;
+ for(; *s; s++)
+ if(*s == '@')
+ nat++;
+ else if(!isalpha(*s) && !isdigit(*s) && !strchr("_.-+/", *s))
+ return 0;
+ return nat==1;
+}
+
+char addrdelim[] = "/[ \t\\n<>()\\[\\]]/";
+char*
+expandaddr(Window *w, Event *e)
+{
+ char *s;
+ long q0, q1;
+
+ if(e->q0 != e->q1) /* cannot happen */
+ return nil;
+
+ q0 = eval(w, "#%d-%s", e->q0, addrdelim);
+ if(q0 == -1) /* bad char not found */
+ q0 = 0;
+ else /* increment past bad char */
+ q0++;
+
+ q1 = eval(w, "#%d+%s", e->q0, addrdelim);
+ if(q1 < 0){
+ q1 = eval(w, "$");
+ if(q1 < 0)
+ return nil;
+ }
+ if(q0 >= q1)
+ return nil;
+ s = emalloc((q1-q0)*UTFmax+1);
+ winread(w, q0, q1, s);
+ return s;
+}
+
+int
+replytoaddr(Window *w, Message *m, Event *e, char *s)
+{
+ int did;
+ char *buf;
+ Plumbmsg *pm;
+
+ buf = nil;
+ did = 0;
+ if(e->flag & 2){
+ /* autoexpanded; use our own bigger expansion */
+ buf = expandaddr(w, e);
+ if(buf == nil)
+ return 0;
+ s = buf;
+ }
+ if(isemail(s)){
+ did = 1;
+ pm = emalloc(sizeof(Plumbmsg));
+ pm->src = estrdup("Mail");
+ pm->dst = estrdup("sendmail");
+ pm->data = estrdup(s);
+ pm->ndata = -1;
+ if(m->subject && m->subject[0]){
+ pm->attr = emalloc(sizeof(Plumbattr));
+ pm->attr->name = estrdup("Subject");
+ if(tolower(m->subject[0]) != 'r' || tolower(m->subject[1]) != 'e' || m->subject[2] != ':')
+ pm->attr->value = estrstrdup("Re: ", m->subject);
+ else
+ pm->attr->value = estrdup(m->subject);
+ pm->attr->next = nil;
+ }
+ if(plumbsend(plumbsendfd, pm) < 0)
+ fprint(2, "error writing plumb message: %r\n");
+ plumbfree(pm);
+ }
+ free(buf);
+ return did;
+}
+
+
+void
+mesgctl(void *v)
+{
+ Message *m;
+ Window *w;
+ Event *e, *eq, *e2, *ea;
+ int na, nopen, i, j;
+ char *os, *s, *t, *buf;
+
+ m = v;
+ w = m->w;
+ threadsetname("mesgctl");
+ proccreate(wineventproc, w, STACK);
+ for(;;){
+ e = recvp(w->cevent);
+ switch(e->c1){
+ default:
+ Unk:
+ print("unknown message %c%c\n", e->c1, e->c2);
+ break;
+
+ case 'E': /* write to body; can't affect us */
+ break;
+
+ case 'F': /* generated by our actions; ignore */
+ break;
+
+ case 'K': /* type away; we don't care */
+ case 'M':
+ switch(e->c2){
+ case 'x': /* mouse only */
+ case 'X':
+ ea = nil;
+ eq = e;
+ if(e->flag & 2){
+ e2 = recvp(w->cevent);
+ eq = e2;
+ }
+ if(e->flag & 8){
+ ea = recvp(w->cevent);
+ recvp(w->cevent);
+ na = ea->nb;
+ }else
+ na = 0;
+ if(eq->q1>eq->q0 && eq->nb==0){
+ s = emalloc((eq->q1-eq->q0)*UTFmax+1);
+ winread(w, eq->q0, eq->q1, s);
+ }else
+ s = estrdup(eq->b);
+ if(na){
+ t = emalloc(strlen(s)+1+na+1);
+ sprint(t, "%s %s", s, ea->b);
+ free(s);
+ s = t;
+ }
+ if(!mesgcommand(m, s)) /* send it back */
+ winwriteevent(w, e);
+ break;
+
+ case 'l': /* mouse only */
+ case 'L':
+ buf = nil;
+ eq = e;
+ if(e->flag & 2){
+ e2 = recvp(w->cevent);
+ eq = e2;
+ }
+ s = eq->b;
+ if(eq->q1>eq->q0 && eq->nb==0){
+ buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
+ winread(w, eq->q0, eq->q1, buf);
+ s = buf;
+ }
+ os = s;
+ nopen = 0;
+ do{
+ /* skip mail box name if present */
+ if(strncmp(s, mbox.name, strlen(mbox.name)) == 0)
+ s += strlen(mbox.name);
+ if(strstr(s, "body") != nil){
+ /* strip any known extensions */
+ for(i=0; exts[i].ext!=nil; i++){
+ j = strlen(exts[i].ext);
+ if(strlen(s)>j && strcmp(s+strlen(s)-j, exts[i].ext)==0){
+ s[strlen(s)-j] = '\0';
+ break;
+ }
+ }
+ if(strlen(s)>5 && strcmp(s+strlen(s)-5, "/body")==0)
+ s[strlen(s)-4] = '\0'; /* leave / in place */
+ }
+ nopen += mesgopen(&mbox, mbox.name, s, m, 0, nil);
+ while(*s!=0 && *s++!='\n')
+ ;
+ }while(*s);
+ if(nopen == 0 && e->c1 == 'L')
+ nopen += replytoaddr(w, m, e, os);
+ if(nopen == 0)
+ winwriteevent(w, e);
+ free(buf);
+ break;
+
+ case 'I': /* modify away; we don't care */
+ case 'D':
+ mesgtagpost(m);
+ /* fall through */
+ case 'd':
+ case 'i':
+ break;
+
+ default:
+ goto Unk;
+ }
+ }
+ }
+}
+
+void
+mesgline(Message *m, char *header, char *value)
+{
+ if(strlen(value) > 0)
+ Bprint(m->w->body, "%s: %s\n", header, value);
+}
+
+int
+isprintable(char *type)
+{
+ int i;
+
+ for(i=0; goodtypes[i]!=nil; i++)
+ if(strcmp(type, goodtypes[i])==0)
+ return 1;
+ return 0;
+}
+
+char*
+ext(char *type)
+{
+ int i;
+
+ for(i=0; exts[i].type!=nil; i++)
+ if(strcmp(type, exts[i].type)==0)
+ return exts[i].ext;
+ return "";
+}
+
+void
+mimedisplay(Message *m, char *name, char *rootdir, Window *w, int fileonly)
+{
+ char *dest;
+
+ if(strcmp(m->disposition, "file")==0 || strlen(m->filename)!=0){
+ if(strlen(m->filename) == 0){
+ dest = estrdup(m->name);
+ dest[strlen(dest)-1] = '\0';
+ }else
+ dest = estrdup(m->filename);
+ if(m->filename[0] != '/')
+ dest = egrow(estrdup(home), "/", dest);
+ Bprint(w->body, "\tcp %s%sbody%s %q\n", rootdir, name, ext(m->type), dest);
+ free(dest);
+ }else if(!fileonly)
+ Bprint(w->body, "\tfile is %s%sbody%s\n", rootdir, name, ext(m->type));
+}
+
+void
+printheader(char *dir, Biobuf *b, char **okheaders)
+{
+ char *s;
+ char *lines[100];
+ int i, j, n;
+
+ s = readfile(dir, "header", nil);
+ if(s == nil)
+ return;
+ n = getfields(s, lines, nelem(lines), 0, "\n");
+ for(i=0; i<n; i++)
+ for(j=0; okheaders[j]; j++)
+ if(cistrncmp(lines[i], okheaders[j], strlen(okheaders[j])) == 0)
+ Bprint(b, "%s\n", lines[i]);
+ free(s);
+}
+
+void
+mesgload(Message *m, char *rootdir, char *file, Window *w)
+{
+ char *s, *subdir, *name, *dir;
+ Message *mp, *thisone;
+ int n;
+
+ dir = estrstrdup(rootdir, file);
+
+ if(strcmp(m->type, "message/rfc822") != 0){ /* suppress headers of envelopes */
+ if(strlen(m->from) > 0){
+ Bprint(w->body, "From: %s\n", m->from);
+ mesgline(m, "Date", m->date);
+ mesgline(m, "To", m->to);
+ mesgline(m, "CC", m->cc);
+ mesgline(m, "Subject", m->subject);
+ printheader(dir, w->body, extraheaders);
+ }else{
+ printheader(dir, w->body, okheaders);
+ printheader(dir, w->body, extraheaders);
+ }
+ Bprint(w->body, "\n");
+ }
+
+ if(m->level == 1 && m->recursed == 0){
+ m->recursed = 1;
+ readmbox(m, rootdir, m->name);
+ }
+ if(m->head == nil){ /* single part message */
+ if(strcmp(m->type, "text")==0 || strncmp(m->type, "text/", 5)==0){
+ mimedisplay(m, m->name, rootdir, w, 1);
+ s = readbody(m->type, dir, &n);
+ winwritebody(w, s, n);
+ free(s);
+ }else
+ mimedisplay(m, m->name, rootdir, w, 0);
+ }else{
+ /* multi-part message, either multipart/* or message/rfc822 */
+ thisone = nil;
+ if(strcmp(m->type, "multipart/alternative") == 0){
+ thisone = m->head; /* in case we can't find a good one */
+ for(mp=m->head; mp!=nil; mp=mp->next)
+ if(isprintable(mp->type)){
+ thisone = mp;
+ break;
+ }
+ }
+ for(mp=m->head; mp!=nil; mp=mp->next){
+ if(thisone!=nil && mp!=thisone)
+ continue;
+ subdir = estrstrdup(dir, mp->name);
+ name = estrstrdup(file, mp->name);
+ /* skip first element in name because it's already in window name */
+ if(mp != m->head)
+ Bprint(w->body, "\n===> %s (%s) [%s]\n", strchr(name, '/')+1, mp->type, mp->disposition);
+ if(strcmp(mp->type, "text")==0 || strncmp(mp->type, "text/", 5)==0){
+ mimedisplay(mp, name, rootdir, w, 1);
+ printheader(subdir, w->body, okheaders);
+ printheader(subdir, w->body, extraheaders);
+ winwritebody(w, "\n", 1);
+ s = readbody(mp->type, subdir, &n);
+ winwritebody(w, s, n);
+ free(s);
+ }else{
+ if(strncmp(mp->type, "multipart/", 10)==0 || strcmp(mp->type, "message/rfc822")==0){
+ mp->w = w;
+ mesgload(mp, rootdir, name, w);
+ mp->w = nil;
+ }else
+ mimedisplay(mp, name, rootdir, w, 0);
+ }
+ free(name);
+ free(subdir);
+ }
+ }
+ free(dir);
+}
+
+int
+tokenizec(char *str, char **args, int max, char *splitc)
+{
+ int na;
+ int intok = 0;
+
+ if(max <= 0)
+ return 0;
+ for(na=0; *str != '\0';str++){
+ if(strchr(splitc, *str) == nil){
+ if(intok)
+ continue;
+ args[na++] = str;
+ intok = 1;
+ }else{
+ /* it's a separator/skip character */
+ *str = '\0';
+ if(intok){
+ intok = 0;
+ if(na >= max)
+ break;
+ }
+ }
+ }
+ return na;
+}
+
+Message*
+mesglookup(Message *mbox, char *name, char *digest)
+{
+ int n;
+ Message *m;
+ char *t;
+
+ if(digest){
+ /* can find exactly */
+ for(m=mbox->head; m!=nil; m=m->next)
+ if(strcmp(digest, m->digest) == 0)
+ break;
+ return m;
+ }
+
+ n = strlen(name);
+ if(n == 0)
+ return nil;
+ if(name[n-1] == '/')
+ t = estrdup(name);
+ else
+ t = estrstrdup(name, "/");
+ for(m=mbox->head; m!=nil; m=m->next)
+ if(strcmp(t, m->name) == 0)
+ break;
+ free(t);
+ return m;
+}
+
+/*
+ * Find plumb port, knowing type is text, given file name (by extension)
+ */
+int
+plumbportbysuffix(char *file)
+{
+ char *suf;
+ int i, nsuf, nfile;
+
+ nfile = strlen(file);
+ for(i=0; ports[i].type!=nil; i++){
+ suf = ports[i].suffix;
+ nsuf = strlen(suf);
+ if(nfile > nsuf)
+ if(cistrncmp(file+nfile-nsuf, suf, nsuf) == 0)
+ return i;
+ }
+ return 0;
+}
+
+/*
+ * Find plumb port using type and file name (by extension)
+ */
+int
+plumbport(char *type, char *file)
+{
+ int i;
+
+ for(i=0; ports[i].type!=nil; i++)
+ if(strncmp(type, ports[i].type, strlen(ports[i].type)) == 0)
+ return i;
+ /* see if it's a text type */
+ for(i=0; goodtypes[i]!=nil; i++)
+ if(strncmp(type, goodtypes[i], strlen(goodtypes[i])) == 0)
+ return plumbportbysuffix(file);
+ return -1;
+}
+
+void
+plumb(Message *m, char *dir)
+{
+ int i;
+ char *port;
+ Plumbmsg *pm;
+
+ if(strlen(m->type) == 0)
+ return;
+ i = plumbport(m->type, m->filename);
+ if(i < 0)
+ fprint(2, "can't find destination for message subpart\n");
+ else{
+ port = ports[i].port;
+ pm = emalloc(sizeof(Plumbmsg));
+ pm->src = estrdup("Mail");
+ if(port)
+ pm->dst = estrdup(port);
+ else
+ pm->dst = nil;
+ pm->wdir = nil;
+ pm->type = estrdup("text");
+ pm->ndata = -1;
+ pm->data = estrstrdup(dir, "body");
+ pm->data = eappend(pm->data, "", ports[i].suffix);
+ if(plumbsend(plumbsendfd, pm) < 0)
+ fprint(2, "error writing plumb message: %r\n");
+ plumbfree(pm);
+ }
+}
+
+int
+mesgopen(Message *mbox, char *dir, char *s, Message *mesg, int plumbed, char *digest)
+{
+ char *t, *u, *v;
+ Message *m;
+ char *direlem[10];
+ int i, ndirelem, reuse;
+
+ /* find white-space-delimited first word */
+ for(t=s; *t!='\0' && !isspace(*t); t++)
+ ;
+ u = emalloc(t-s+1);
+ memmove(u, s, t-s);
+ /* separate it on slashes */
+ ndirelem = tokenizec(u, direlem, nelem(direlem), "/");
+ if(ndirelem <= 0){
+ Error:
+ free(u);
+ return 0;
+ }
+ if(plumbed){
+ write(wctlfd, "top", 3);
+ write(wctlfd, "current", 7);
+ }
+ /* open window for message */
+ m = mesglookup(mbox, direlem[0], digest);
+ if(m == nil)
+ goto Error;
+ if(mesg!=nil && m!=mesg) /* string looked like subpart but isn't part of this message */
+ goto Error;
+ if(m->opened == 0){
+ if(m->w == nil){
+ reuse = 0;
+ m->w = newwindow();
+ }else{
+ reuse = 1;
+ /* re-use existing window */
+ if(winsetaddr(m->w, "0,$", 1)){
+ if(m->w->data < 0)
+ m->w->data = winopenfile(m->w, "data");
+ write(m->w->data, "", 0);
+ }
+ }
+ v = estrstrdup(mbox->name, m->name);
+ winname(m->w, v);
+ free(v);
+ if(!reuse){
+ if(m->deleted)
+ wintagwrite(m->w, "Q Reply all UnDelmesg Save ", 2+6+4+10+5);
+ else
+ wintagwrite(m->w, "Q Reply all Delmesg Save ", 2+6+4+8+5);
+ }
+ threadcreate(mesgctl, m, STACK);
+ winopenbody(m->w, OWRITE);
+ mesgload(m, dir, m->name, m->w);
+ winclosebody(m->w);
+ winclean(m->w);
+ m->opened = 1;
+ if(ndirelem == 1){
+ free(u);
+ return 1;
+ }
+ }
+ if(ndirelem == 1 && plumbport(m->type, m->filename) <= 0){
+ /* make sure dot is visible */
+ ctlprint(m->w->ctl, "show\n");
+ return 0;
+ }
+ /* walk to subpart */
+ dir = estrstrdup(dir, m->name);
+ for(i=1; i<ndirelem; i++){
+ m = mesglookup(m, direlem[i], digest);
+ if(m == nil)
+ break;
+ dir = egrow(dir, m->name, nil);
+ }
+ if(m != nil && plumbport(m->type, m->filename) > 0)
+ plumb(m, dir);
+ free(dir);
+ free(u);
+ return 1;
+}
+
+void
+rewritembox(Window *w, Message *mbox)
+{
+ Message *m, *next;
+ char *deletestr, *t;
+ int nopen;
+
+ deletestr = estrstrdup("delete ", fsname);
+
+ nopen = 0;
+ for(m=mbox->head; m!=nil; m=next){
+ next = m->next;
+ if(m->deleted == 0)
+ continue;
+ if(m->opened){
+ nopen++;
+ continue;
+ }
+ if(m->writebackdel){
+ /* messages deleted by plumb message are not removed again */
+ t = estrdup(m->name);
+ if(strlen(t) > 0)
+ t[strlen(t)-1] = '\0';
+ deletestr = egrow(deletestr, " ", t);
+ }
+ mesgmenudel(w, mbox, m);
+ mesgdel(mbox, m);
+ }
+ if(write(mbox->ctlfd, deletestr, strlen(deletestr)) < 0)
+ fprint(2, "Mail: warning: error removing mail message files: %r\n");
+ free(deletestr);
+ winselect(w, "0", 0);
+ if(nopen == 0)
+ winclean(w);
+ mbox->dirty = 0;
+}
+
+/* name is a full file name, but it might not belong to us */
+Message*
+mesglookupfile(Message *mbox, char *name, char *digest)
+{
+ int k, n;
+
+ k = strlen(name);
+ n = strlen(mbox->name);
+ if(k==0 || strncmp(name, mbox->name, n) != 0){
+// fprint(2, "Mail: message %s not in this mailbox\n", name);
+ return nil;
+ }
+ return mesglookup(mbox, name+n, digest);
+}
--- /dev/null
+</$objtype/mkfile
+
+TARG=Mail
+OFILES=\
+ html.$O\
+ mail.$O\
+ mesg.$O\
+ reply.$O\
+ util.$O\
+ win.$O
+
+HFILES=dat.h
+LIB=
+
+BIN=/acme/bin/$objtype
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+
+</sys/src/cmd/mkone
+
+$O.out: $OFILES
+ $LD -o $target $LDFLAGS $OFILES
+
+syms:V:
+ 8c -a mail.c >syms
+ 8c -aa mesg.c reply.c util.c win.c >>syms
+
--- /dev/null
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <ctype.h>
+#include <plumb.h>
+#include "dat.h"
+
+static int replyid;
+
+int
+quote(Message *m, Biobuf *b, char *dir, char *quotetext)
+{
+ char *body, *type;
+ int i, n, nlines;
+ char **lines;
+
+ if(quotetext){
+ body = quotetext;
+ n = strlen(body);
+ type = nil;
+ }else{
+ /* look for first textual component to quote */
+ type = readfile(dir, "type", &n);
+ if(type == nil){
+ print("no type in %s\n", dir);
+ return 0;
+ }
+ if(strncmp(type, "multipart/", 10)==0 || strncmp(type, "message/", 8)==0){
+ dir = estrstrdup(dir, "1/");
+ if(quote(m, b, dir, nil)){
+ free(type);
+ free(dir);
+ return 1;
+ }
+ free(dir);
+ }
+ if(strncmp(type, "text", 4) != 0){
+ free(type);
+ return 0;
+ }
+ body = readbody(m->type, dir, &n);
+ if(body == nil)
+ return 0;
+ }
+ nlines = 0;
+ for(i=0; i<n; i++)
+ if(body[i] == '\n')
+ nlines++;
+ nlines++;
+ lines = emalloc(nlines*sizeof(char*));
+ nlines = getfields(body, lines, nlines, 0, "\n");
+ /* delete leading and trailing blank lines */
+ i = 0;
+ while(i<nlines && lines[i][0]=='\0')
+ i++;
+ while(i<nlines && lines[nlines-1][0]=='\0')
+ nlines--;
+ while(i < nlines){
+ Bprint(b, ">%s%s\n", lines[i][0]=='>'? "" : " ", lines[i]);
+ i++;
+ }
+ free(lines);
+ free(body); /* will free quotetext if non-nil */
+ free(type);
+ return 1;
+}
+
+void
+mkreply(Message *m, char *label, char *to, Plumbattr *attr, char *quotetext)
+{
+ Message *r;
+ char *dir, *t;
+ int quotereply;
+ Plumbattr *a;
+
+ quotereply = (label[0] == 'Q');
+ r = emalloc(sizeof(Message));
+ r->isreply = 1;
+ if(m != nil)
+ r->replyname = estrdup(m->name);
+ r->next = replies.head;
+ r->prev = nil;
+ if(replies.head != nil)
+ replies.head->prev = r;
+ replies.head = r;
+ if(replies.tail == nil)
+ replies.tail = r;
+ r->name = emalloc(strlen(mbox.name)+strlen(label)+10);
+ sprint(r->name, "%s%s%d", mbox.name, label, ++replyid);
+ r->w = newwindow();
+ winname(r->w, r->name);
+ ctlprint(r->w->ctl, "cleartag");
+ wintagwrite(r->w, "fmt Look Post Undo", 4+5+5+4);
+ r->tagposted = 1;
+ threadcreate(mesgctl, r, STACK);
+ winopenbody(r->w, OWRITE);
+ if(to!=nil && to[0]!='\0')
+ Bprint(r->w->body, "%s\n", to);
+ for(a=attr; a; a=a->next)
+ Bprint(r->w->body, "%s: %s\n", a->name, a->value);
+ dir = nil;
+ if(m != nil){
+ dir = estrstrdup(mbox.name, m->name);
+ if(to == nil && attr == nil){
+ /* Reply goes to replyto; Reply all goes to From and To and CC */
+ if(strstr(label, "all") == nil)
+ Bprint(r->w->body, "To: %s\n", m->replyto);
+ else{ /* Replyall */
+ if(strlen(m->from) > 0)
+ Bprint(r->w->body, "To: %s\n", m->from);
+ if(strlen(m->to) > 0)
+ Bprint(r->w->body, "To: %s\n", m->to);
+ if(strlen(m->cc) > 0)
+ Bprint(r->w->body, "CC: %s\n", m->cc);
+ }
+ }
+ if(strlen(m->subject) > 0){
+ t = "Subject: Re: ";
+ if(strlen(m->subject) >= 3)
+ if(tolower(m->subject[0])=='r' && tolower(m->subject[1])=='e' && m->subject[2]==':')
+ t = "Subject: ";
+ Bprint(r->w->body, "%s%s\n", t, m->subject);
+ }
+ if(!quotereply){
+ Bprint(r->w->body, "Include: %sraw\n", dir);
+ free(dir);
+ }
+ }
+ Bprint(r->w->body, "\n");
+ if(m == nil)
+ Bprint(r->w->body, "\n");
+ else if(quotereply){
+ quote(m, r->w->body, dir, quotetext);
+ free(dir);
+ }
+ winclosebody(r->w);
+ if(m==nil && (to==nil || to[0]=='\0'))
+ winselect(r->w, "0", 0);
+ else
+ winselect(r->w, "$", 0);
+ winclean(r->w);
+ windormant(r->w);
+}
+
+void
+delreply(Message *m)
+{
+ if(m->next == nil)
+ replies.tail = m->prev;
+ else
+ m->next->prev = m->prev;
+ if(m->prev == nil)
+ replies.head = m->next;
+ else
+ m->prev->next = m->next;
+ mesgfreeparts(m);
+ free(m);
+}
+
+/* copy argv to stack and free the incoming strings, so we don't leak argument vectors */
+void
+buildargv(char **inargv, char *argv[NARGS+1], char args[NARGCHAR])
+{
+ int i, n;
+ char *s, *a;
+
+ s = args;
+ for(i=0; i<NARGS; i++){
+ a = inargv[i];
+ if(a == nil)
+ break;
+ n = strlen(a)+1;
+ if((s-args)+n >= NARGCHAR) /* too many characters */
+ break;
+ argv[i] = s;
+ memmove(s, a, n);
+ s += n;
+ free(a);
+ }
+ argv[i] = nil;
+}
+
+void
+execproc(void *v)
+{
+ struct Exec *e;
+ int p[2], q[2];
+ char *prog;
+ char *argv[NARGS+1], args[NARGCHAR];
+
+ e = v;
+ p[0] = e->p[0];
+ p[1] = e->p[1];
+ q[0] = e->q[0];
+ q[1] = e->q[1];
+ prog = e->prog; /* known not to be malloc'ed */
+ rfork(RFFDG);
+ sendul(e->sync, 1);
+ buildargv(e->argv, argv, args);
+ free(e->argv);
+ chanfree(e->sync);
+ free(e);
+ dup(p[0], 0);
+ close(p[0]);
+ close(p[1]);
+ if(q[0]){
+ dup(q[1], 1);
+ close(q[0]);
+ close(q[1]);
+ }
+ procexec(nil, prog, argv);
+//fprint(2, "exec: %s", e->prog);
+//{int i;
+//for(i=0; argv[i]; i++) print(" '%s'", argv[i]);
+//print("\n");
+//}
+//argv[0] = "cat";
+//argv[1] = nil;
+//procexec(nil, "/bin/cat", argv);
+ fprint(2, "Mail: can't exec %s: %r\n", prog);
+ threadexits("can't exec");
+}
+
+enum{
+ ATTACH,
+ BCC,
+ CC,
+ FROM,
+ INCLUDE,
+ TO,
+};
+
+char *headers[] = {
+ "attach:",
+ "bcc:",
+ "cc:",
+ "from:",
+ "include:",
+ "to:",
+ nil,
+};
+
+int
+whichheader(char *h)
+{
+ int i;
+
+ for(i=0; headers[i]!=nil; i++)
+ if(cistrcmp(h, headers[i]) == 0)
+ return i;
+ return -1;
+}
+
+char *tolist[200];
+char *cclist[200];
+char *bcclist[200];
+int ncc, nbcc, nto;
+char *attlist[200];
+char included[200];
+
+int
+addressed(char *name)
+{
+ int i;
+
+ for(i=0; i<nto; i++)
+ if(strcmp(name, tolist[i]) == 0)
+ return 1;
+ for(i=0; i<ncc; i++)
+ if(strcmp(name, cclist[i]) == 0)
+ return 1;
+ for(i=0; i<nbcc; i++)
+ if(strcmp(name, bcclist[i]) == 0)
+ return 1;
+ return 0;
+}
+
+char*
+skipbl(char *s, char *e)
+{
+ while(s < e){
+ if(*s!=' ' && *s!='\t' && *s!=',')
+ break;
+ s++;
+ }
+ return s;
+}
+
+char*
+findbl(char *s, char *e)
+{
+ while(s < e){
+ if(*s==' ' || *s=='\t' || *s==',')
+ break;
+ s++;
+ }
+ return s;
+}
+
+/*
+ * comma-separate possibly blank-separated strings in line; e points before newline
+ */
+void
+commas(char *s, char *e)
+{
+ char *t;
+
+ /* may have initial blanks */
+ s = skipbl(s, e);
+ while(s < e){
+ s = findbl(s, e);
+ if(s == e)
+ break;
+ t = skipbl(s, e);
+ if(t == e) /* no more words */
+ break;
+ /* patch comma */
+ *s++ = ',';
+ while(s < t)
+ *s++ = ' ';
+ }
+}
+
+int
+print2(int fd, int ofd, char *fmt, ...)
+{
+ int m, n;
+ char *s;
+ va_list arg;
+
+ va_start(arg, fmt);
+ s = vsmprint(fmt, arg);
+ va_end(arg);
+ if(s == nil)
+ return -1;
+ m = strlen(s);
+ n = write(fd, s, m);
+ if(ofd > 0)
+ write(ofd, s, m);
+ return n;
+}
+
+void
+write2(int fd, int ofd, char *buf, int n, int nofrom)
+{
+ char *from, *p;
+ int m;
+
+ write(fd, buf, n);
+
+ if(ofd <= 0)
+ return;
+
+ if(nofrom == 0){
+ write(ofd, buf, n);
+ return;
+ }
+
+ /* need to escape leading From lines to avoid corrupting 'outgoing' mailbox */
+ for(p=buf; *p; p+=m){
+ from = cistrstr(p, "from");
+ if(from == nil)
+ m = n;
+ else
+ m = from - p;
+ if(m > 0)
+ write(ofd, p, m);
+ if(from){
+ if(p==buf || from[-1]=='\n')
+ write(ofd, " ", 1); /* escape with space if From is at start of line */
+ write(ofd, from, 4);
+ m += 4;
+ }
+ n -= m;
+ }
+}
+
+void
+mesgsend(Message *m)
+{
+ char *s, *body, *to;
+ int i, j, h, n, natt, p[2];
+ struct Exec *e;
+ Channel *sync;
+ int first, nfld, delit, ofd;
+ char *copy, *fld[100], *now;
+
+ body = winreadbody(m->w, &n);
+ /* assemble to: list from first line, to: line, and cc: line */
+ nto = 0;
+ natt = 0;
+ ncc = 0;
+ nbcc = 0;
+ first = 1;
+ to = body;
+ for(;;){
+ for(s=to; *s!='\n'; s++)
+ if(*s == '\0'){
+ free(body);
+ return;
+ }
+ if(s++ == to) /* blank line */
+ break;
+ /* make copy of line to tokenize */
+ copy = emalloc(s-to);
+ memmove(copy, to, s-to);
+ copy[s-to-1] = '\0';
+ nfld = tokenizec(copy, fld, nelem(fld), ", \t");
+ if(nfld == 0){
+ free(copy);
+ break;
+ }
+ n -= s-to;
+ switch(h = whichheader(fld[0])){
+ case TO:
+ case FROM:
+ delit = 1;
+ commas(to+strlen(fld[0]), s-1);
+ for(i=1; i<nfld && nto<nelem(tolist); i++)
+ if(!addressed(fld[i]))
+ tolist[nto++] = estrdup(fld[i]);
+ break;
+ case BCC:
+ delit = 1;
+ commas(to+strlen(fld[0]), s-1);
+ for(i=1; i<nfld && nbcc<nelem(bcclist); i++)
+ if(!addressed(fld[i]))
+ bcclist[nbcc++] = estrdup(fld[i]);
+ break;
+ case CC:
+ delit = 1;
+ commas(to+strlen(fld[0]), s-1);
+ for(i=1; i<nfld && ncc<nelem(cclist); i++)
+ if(!addressed(fld[i]))
+ cclist[ncc++] = estrdup(fld[i]);
+ break;
+ case ATTACH:
+ case INCLUDE:
+ delit = 1;
+ for(i=1; i<nfld && natt<nelem(attlist); i++){
+ attlist[natt] = estrdup(fld[i]);
+ included[natt++] = (h == INCLUDE);
+ }
+ break;
+ default:
+ if(first){
+ delit = 1;
+ for(i=0; i<nfld && nto<nelem(tolist); i++)
+ tolist[nto++] = estrdup(fld[i]);
+ }else /* ignore it */
+ delit = 0;
+ break;
+ }
+ if(delit){
+ /* delete line from body */
+ memmove(to, s, n+1);
+ }else
+ to = s;
+ free(copy);
+ first = 0;
+ }
+
+ ofd = open(outgoing, OWRITE|OCEXEC); /* no error check necessary */
+ if(ofd > 0){
+ /* From dhog Fri Aug 24 22:13:00 EDT 2001 */
+ now = ctime(time(0));
+ fprint(ofd, "From %s %s", user, now);
+ fprint(ofd, "From: %s\n", user);
+ fprint(ofd, "Date: %s", now);
+ for(i=0; i<natt; i++)
+ if(included[i])
+ fprint(ofd, "Include: %s\n", attlist[i]);
+ else
+ fprint(ofd, "Attach: %s\n", attlist[i]);
+ /* needed because mail is by default Latin-1 */
+ fprint(ofd, "Content-Type: text/plain; charset=\"UTF-8\"\n");
+ fprint(ofd, "Content-Transfer-Encoding: 8bit\n");
+ }
+
+ e = emalloc(sizeof(struct Exec));
+ if(pipe(p) < 0)
+ error("can't create pipe: %r");
+ e->p[0] = p[0];
+ e->p[1] = p[1];
+ e->prog = "/bin/upas/marshal";
+ e->argv = emalloc((1+1+2+4*natt+1)*sizeof(char*));
+ e->argv[0] = estrdup("marshal");
+ e->argv[1] = estrdup("-8");
+ j = 2;
+ if(m->replyname){
+ e->argv[j++] = estrdup("-R");
+ e->argv[j++] = estrstrdup(mbox.name, m->replyname);
+ }
+ for(i=0; i<natt; i++){
+ if(included[i])
+ e->argv[j++] = estrdup("-A");
+ else
+ e->argv[j++] = estrdup("-a");
+ e->argv[j++] = estrdup(attlist[i]);
+ }
+ sync = chancreate(sizeof(int), 0);
+ e->sync = sync;
+ proccreate(execproc, e, EXECSTACK);
+ recvul(sync);
+ close(p[0]);
+
+ /* using marshal -8, so generate rfc822 headers */
+ if(nto > 0){
+ print2(p[1], ofd, "To: ");
+ for(i=0; i<nto-1; i++)
+ print2(p[1], ofd, "%s, ", tolist[i]);
+ print2(p[1], ofd, "%s\n", tolist[i]);
+ }
+ if(ncc > 0){
+ print2(p[1], ofd, "CC: ");
+ for(i=0; i<ncc-1; i++)
+ print2(p[1], ofd, "%s, ", cclist[i]);
+ print2(p[1], ofd, "%s\n", cclist[i]);
+ }
+ if(nbcc > 0){
+ print2(p[1], ofd, "BCC: ");
+ for(i=0; i<nbcc-1; i++)
+ print2(p[1], ofd, "%s, ", bcclist[i]);
+ print2(p[1], ofd, "%s\n", bcclist[i]);
+ }
+
+ i = strlen(body);
+ if(i > 0)
+ write2(p[1], ofd, body, i, 1);
+
+ /* guarantee a blank line, to ensure attachments are separated from body */
+ if(i==0 || body[i-1]!='\n')
+ write2(p[1], ofd, "\n\n", 2, 0);
+ else if(i>1 && body[i-2]!='\n')
+ write2(p[1], ofd, "\n", 1, 0);
+
+ /* these look like pseudo-attachments in the "outgoing" box */
+ if(ofd>0 && natt>0){
+ for(i=0; i<natt; i++)
+ if(included[i])
+ fprint(ofd, "=====> Include: %s\n", attlist[i]);
+ else
+ fprint(ofd, "=====> Attach: %s\n", attlist[i]);
+ }
+ if(ofd > 0)
+ write(ofd, "\n", 1);
+
+ for(i=0; i<natt; i++)
+ free(attlist[i]);
+ close(ofd);
+ close(p[1]);
+ free(body);
+
+ if(m->replyname != nil)
+ mesgmenumark(mbox.w, m->replyname, "\t[replied]");
+ if(m->name[0] == '/')
+ s = estrdup(m->name);
+ else
+ s = estrstrdup(mbox.name, m->name);
+ s = egrow(s, "-R", nil);
+ winname(m->w, s);
+ free(s);
+ winclean(m->w);
+ /* mark message unopened because it's no longer the original message */
+ m->opened = 0;
+}
--- /dev/null
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <plumb.h>
+#include "dat.h"
+
+void*
+emalloc(uint n)
+{
+ void *p;
+
+ p = malloc(n);
+ if(p == nil)
+ error("can't malloc: %r");
+ memset(p, 0, n);
+ setmalloctag(p, getcallerpc(&n));
+ return p;
+}
+
+void*
+erealloc(void *p, uint n)
+{
+ p = realloc(p, n);
+ if(p == nil)
+ error("can't realloc: %r");
+ setmalloctag(p, getcallerpc(&n));
+ return p;
+}
+
+char*
+estrdup(char *s)
+{
+ char *t;
+
+ t = emalloc(strlen(s)+1);
+ strcpy(t, s);
+ return t;
+}
+
+char*
+estrstrdup(char *s, char *t)
+{
+ char *u;
+
+ u = emalloc(strlen(s)+strlen(t)+1);
+ strcpy(u, s);
+ strcat(u, t);
+ return u;
+}
+
+char*
+eappend(char *s, char *sep, char *t)
+{
+ char *u;
+
+ if(t == nil)
+ u = estrstrdup(s, sep);
+ else{
+ u = emalloc(strlen(s)+strlen(sep)+strlen(t)+1);
+ strcpy(u, s);
+ strcat(u, sep);
+ strcat(u, t);
+ }
+ free(s);
+ return u;
+}
+
+char*
+egrow(char *s, char *sep, char *t)
+{
+ s = eappend(s, sep, t);
+ free(t);
+ return s;
+}
+
+void
+error(char *fmt, ...)
+{
+ Fmt f;
+ char buf[64];
+ va_list arg;
+
+ fmtfdinit(&f, 2, buf, sizeof buf);
+ fmtprint(&f, "Mail: ");
+ va_start(arg, fmt);
+ fmtvprint(&f, fmt, arg);
+ va_end(arg);
+ fmtprint(&f, "\n");
+ fmtfdflush(&f);
+ threadexitsall(fmt);
+}
+
+void
+ctlprint(int fd, char *fmt, ...)
+{
+ int n;
+ va_list arg;
+
+ va_start(arg, fmt);
+ n = vfprint(fd, fmt, arg);
+ va_end(arg);
+ if(n <= 0)
+ error("control file write error: %r");
+}
--- /dev/null
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <plumb.h>
+#include "dat.h"
+
+Window*
+newwindow(void)
+{
+ char buf[12];
+ Window *w;
+
+ w = emalloc(sizeof(Window));
+ w->ctl = open("/mnt/wsys/new/ctl", ORDWR|OCEXEC);
+ if(w->ctl<0 || read(w->ctl, buf, 12)!=12)
+ error("can't open window ctl file: %r");
+ ctlprint(w->ctl, "noscroll\n");
+ w->id = atoi(buf);
+ w->event = winopenfile(w, "event");
+ w->addr = -1; /* will be opened when needed */
+ w->body = nil;
+ w->data = -1;
+ w->cevent = chancreate(sizeof(Event*), 0);
+ return w;
+}
+
+void
+winsetdump(Window *w, char *dir, char *cmd)
+{
+ if(dir != nil)
+ ctlprint(w->ctl, "dumpdir %s\n", dir);
+ if(cmd != nil)
+ ctlprint(w->ctl, "dump %s\n", cmd);
+}
+
+void
+wineventproc(void *v)
+{
+ Window *w;
+ int i;
+
+ w = v;
+ for(i=0; ; i++){
+ if(i >= NEVENT)
+ i = 0;
+ wingetevent(w, &w->e[i]);
+ sendp(w->cevent, &w->e[i]);
+ }
+}
+
+static int
+winopenfile1(Window *w, char *f, int m)
+{
+ char buf[64];
+ int fd;
+
+ sprint(buf, "/mnt/wsys/%d/%s", w->id, f);
+ fd = open(buf, m|OCEXEC);
+ if(fd < 0)
+ error("can't open window file %s: %r", f);
+ return fd;
+}
+
+int
+winopenfile(Window *w, char *f)
+{
+ return winopenfile1(w, f, ORDWR);
+}
+
+void
+wintagwrite(Window *w, char *s, int n)
+{
+ int fd;
+
+ fd = winopenfile(w, "tag");
+ if(write(fd, s, n) != n)
+ error("tag write: %r");
+ close(fd);
+}
+
+void
+winname(Window *w, char *s)
+{
+ ctlprint(w->ctl, "name %s\n", s);
+}
+
+void
+winopenbody(Window *w, int mode)
+{
+ char buf[256];
+
+ sprint(buf, "/mnt/wsys/%d/body", w->id);
+ w->body = Bopen(buf, mode|OCEXEC);
+ if(w->body == nil)
+ error("can't open window body file: %r");
+}
+
+void
+winclosebody(Window *w)
+{
+ if(w->body != nil){
+ Bterm(w->body);
+ w->body = nil;
+ }
+}
+
+void
+winwritebody(Window *w, char *s, int n)
+{
+ if(w->body == nil)
+ winopenbody(w, OWRITE);
+ if(Bwrite(w->body, s, n) != n)
+ error("write error to window: %r");
+}
+
+int
+wingetec(Window *w)
+{
+ if(w->nbuf == 0){
+ w->nbuf = read(w->event, w->buf, sizeof w->buf);
+ if(w->nbuf <= 0){
+ /* probably because window has exited, and only called by wineventproc, so just shut down */
+ threadexits(nil);
+ }
+ w->bufp = w->buf;
+ }
+ w->nbuf--;
+ return *w->bufp++;
+}
+
+int
+wingeten(Window *w)
+{
+ int n, c;
+
+ n = 0;
+ while('0'<=(c=wingetec(w)) && c<='9')
+ n = n*10+(c-'0');
+ if(c != ' ')
+ error("event number syntax");
+ return n;
+}
+
+int
+wingeter(Window *w, char *buf, int *nb)
+{
+ Rune r;
+ int n;
+
+ r = wingetec(w);
+ buf[0] = r;
+ n = 1;
+ if(r >= Runeself) {
+ while(!fullrune(buf, n))
+ buf[n++] = wingetec(w);
+ chartorune(&r, buf);
+ }
+ *nb = n;
+ return r;
+}
+
+void
+wingetevent(Window *w, Event *e)
+{
+ int i, nb;
+
+ e->c1 = wingetec(w);
+ e->c2 = wingetec(w);
+ e->q0 = wingeten(w);
+ e->q1 = wingeten(w);
+ e->flag = wingeten(w);
+ e->nr = wingeten(w);
+ if(e->nr > EVENTSIZE)
+ error("event string too long");
+ e->nb = 0;
+ for(i=0; i<e->nr; i++){
+ e->r[i] = wingeter(w, e->b+e->nb, &nb);
+ e->nb += nb;
+ }
+ e->r[e->nr] = 0;
+ e->b[e->nb] = 0;
+ if(wingetec(w) != '\n')
+ error("event syntax error");
+}
+
+void
+winwriteevent(Window *w, Event *e)
+{
+ fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
+}
+
+void
+winread(Window *w, uint q0, uint q1, char *data)
+{
+ int m, n, nr;
+ char buf[256];
+
+ if(w->addr < 0)
+ w->addr = winopenfile(w, "addr");
+ if(w->data < 0)
+ w->data = winopenfile(w, "data");
+ m = q0;
+ while(m < q1){
+ n = sprint(buf, "#%d", m);
+ if(write(w->addr, buf, n) != n)
+ error("error writing addr: %r");
+ n = read(w->data, buf, sizeof buf);
+ if(n <= 0)
+ error("reading data: %r");
+ nr = utfnlen(buf, n);
+ while(m+nr >q1){
+ do; while(n>0 && (buf[--n]&0xC0)==0x80);
+ --nr;
+ }
+ if(n == 0)
+ break;
+ memmove(data, buf, n);
+ data += n;
+ *data = 0;
+ m += nr;
+ }
+}
+
+void
+windormant(Window *w)
+{
+ if(w->addr >= 0){
+ close(w->addr);
+ w->addr = -1;
+ }
+ if(w->body != nil){
+ Bterm(w->body);
+ w->body = nil;
+ }
+ if(w->data >= 0){
+ close(w->data);
+ w->data = -1;
+ }
+}
+
+
+int
+windel(Window *w, int sure)
+{
+ if(sure)
+ write(w->ctl, "delete\n", 7);
+ else if(write(w->ctl, "del\n", 4) != 4)
+ return 0;
+ /* event proc will die due to read error from event file */
+ windormant(w);
+ close(w->ctl);
+ w->ctl = -1;
+ close(w->event);
+ w->event = -1;
+ return 1;
+}
+
+void
+winclean(Window *w)
+{
+ if(w->body)
+ Bflush(w->body);
+ ctlprint(w->ctl, "clean\n");
+}
+
+int
+winsetaddr(Window *w, char *addr, int errok)
+{
+ if(w->addr < 0)
+ w->addr = winopenfile(w, "addr");
+ if(write(w->addr, addr, strlen(addr)) < 0){
+ if(!errok)
+ error("error writing addr(%s): %r", addr);
+ return 0;
+ }
+ return 1;
+}
+
+int
+winselect(Window *w, char *addr, int errok)
+{
+ if(winsetaddr(w, addr, errok)){
+ ctlprint(w->ctl, "dot=addr\n");
+ return 1;
+ }
+ return 0;
+}
+
+char*
+winreadbody(Window *w, int *np) /* can't use readfile because acme doesn't report the length */
+{
+ char *s;
+ int m, na, n;
+
+ if(w->body != nil)
+ winclosebody(w);
+ winopenbody(w, OREAD);
+ s = nil;
+ na = 0;
+ n = 0;
+ for(;;){
+ if(na < n+512){
+ na += 1024;
+ s = realloc(s, na+1);
+ }
+ m = Bread(w->body, s+n, na-n);
+ if(m <= 0)
+ break;
+ n += m;
+ }
+ s[n] = 0;
+ winclosebody(w);
+ *np = n;
+ return s;
+}
+
+char*
+winselection(Window *w)
+{
+ int fd, m, n;
+ char *buf;
+ char tmp[256];
+
+ fd = winopenfile1(w, "rdsel", OREAD);
+ if(fd < 0)
+ error("can't open rdsel: %r");
+ n = 0;
+ buf = nil;
+ for(;;){
+ m = read(fd, tmp, sizeof tmp);
+ if(m <= 0)
+ break;
+ buf = erealloc(buf, n+m+1);
+ memmove(buf+n, tmp, m);
+ n += m;
+ buf[n] = '\0';
+ }
+ close(fd);
+ return buf;
+}
--- /dev/null
+</$objtype/mkfile
+
+none:VQ:
+ echo mk all, install, clean, nuke, installall, update
+
+all install clean nuke installall update:V:
+ @{cd bin/source; mk $target}
+ @{cd mail/src; mk $target}
+ @{cd news/src; mk $target}
+ @{cd wiki/src; mk $target}
--- /dev/null
+Local nntpfs
+News comp.os.plan9
--- /dev/null
+</$objtype/mkfile
+
+TARG=News
+OFILES=\
+ news.$O\
+ util.$O\
+ win.$O\
+
+HFILES=
+
+BIN=/acme/bin/$objtype
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+ ${TARG:%=/acme/bin/386/%}\
+
+</sys/src/cmd/mkone
--- /dev/null
+/*
+ * Acme interface to nntpfs.
+ */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include "win.h"
+#include <ctype.h>
+
+int canpost;
+int debug;
+int nshow = 20;
+
+int lo; /* next message to look at in dir */
+int hi; /* current hi message in dir */
+char *dir = "/mnt/news";
+char *group;
+char *from;
+
+typedef struct Article Article;
+struct Article {
+ Ref;
+ Article *prev;
+ Article *next;
+ Window *w;
+ int n;
+ int dead;
+ int dirtied;
+ int sayspost;
+ int headers;
+ int ispost;
+};
+
+Article *mlist;
+Window *root;
+
+int
+cistrncmp(char *a, char *b, int n)
+{
+ while(n-- > 0){
+ if(tolower(*a++) != tolower(*b++))
+ return -1;
+ }
+ return 0;
+}
+
+int
+cistrcmp(char *a, char *b)
+{
+ for(;;){
+ if(tolower(*a) != tolower(*b++))
+ return -1;
+ if(*a++ == 0)
+ break;
+ }
+ return 0;
+}
+
+char*
+skipwhite(char *p)
+{
+ while(isspace(*p))
+ p++;
+ return p;
+}
+
+int
+gethi(void)
+{
+ Dir *d;
+ int hi;
+
+ if((d = dirstat(dir)) == nil)
+ return -1;
+ hi = d->qid.vers;
+ free(d);
+ return hi;
+}
+
+char*
+fixfrom(char *s)
+{
+ char *r, *w;
+
+ s = estrdup(s);
+ /* remove quotes */
+ for(r=w=s; *r; r++)
+ if(*r != '"')
+ *w++ = *r;
+ *w = '\0';
+ return s;
+}
+
+char *day[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", };
+char *mon[] = {
+ "Jan", "Feb", "Mar", "Apr",
+ "May", "Jun", "Jul", "Aug",
+ "Sep", "Oct", "Nov", "Dec",
+};
+
+char*
+fixdate(char *s)
+{
+ char *f[10], *m, *t, *wd, tmp[40];
+ int d, i, j, nf, hh, mm;
+
+ nf = tokenize(s, f, nelem(f));
+
+ wd = nil;
+ d = 0;
+ m = nil;
+ t = nil;
+ for(i=0; i<nf; i++){
+ for(j=0; j<7; j++)
+ if(cistrncmp(day[j], f[i], 3)==0)
+ wd = day[j];
+ for(j=0; j<12; j++)
+ if(cistrncmp(mon[j], f[i], 3)==0)
+ m = mon[j];
+ j = atoi(f[i]);
+ if(1 <= j && j <= 31 && d != 0)
+ d = j;
+ if(strchr(f[i], ':'))
+ t = f[i];
+ }
+
+ if(d==0 || wd==nil || m==nil || t==nil)
+ return nil;
+
+ hh = strtol(t, 0, 10);
+ mm = strtol(strchr(t, ':')+1, 0, 10);
+ sprint(tmp, "%s %d %s %d:%.2d", wd, d, m, hh, mm);
+ return estrdup(tmp);
+}
+
+void
+msgheadline(Biobuf *bin, int n, Biobuf *bout)
+{
+ char *p, *q;
+ char *date;
+ char *from;
+ char *subject;
+
+ date = nil;
+ from = nil;
+ subject = nil;
+ while(p = Brdline(bin, '\n')){
+ p[Blinelen(bin)-1] = '\0';
+ if((q = strchr(p, ':')) == nil)
+ continue;
+ *q++ = '\0';
+ if(cistrcmp(p, "from")==0)
+ from = fixfrom(skipwhite(q));
+ else if(cistrcmp(p, "subject")==0)
+ subject = estrdup(skipwhite(q));
+ else if(cistrcmp(p, "date")==0)
+ date = fixdate(skipwhite(q));
+ }
+
+ Bprint(bout, "%d/\t%s", n, from ? from : "");
+ if(date)
+ Bprint(bout, "\t%s", date);
+ if(subject)
+ Bprint(bout, "\n\t%s", subject);
+ Bprint(bout, "\n");
+
+ free(date);
+ free(from);
+ free(subject);
+}
+
+/*
+ * Write the headers for at most nshow messages to b,
+ * starting with hi and working down to lo.
+ * Return number of first message not scanned.
+ */
+int
+adddir(Biobuf *body, int hi, int lo, int nshow)
+{
+ char *p, *q, tmp[40];
+ int i, n;
+ Biobuf *b;
+
+ n = 0;
+ for(i=hi; i>=lo && n<nshow; i--){
+ sprint(tmp, "%d", i);
+ p = estrstrdup(dir, tmp);
+ if(access(p, OREAD) < 0){
+ free(p);
+ break;
+ }
+ q = estrstrdup(p, "/header");
+ free(p);
+ b = Bopen(q, OREAD);
+ free(q);
+ if(b == nil)
+ continue;
+ msgheadline(b, i, body);
+ Bterm(b);
+ n++;
+ }
+ return i;
+}
+
+/*
+ * Show the first nshow messages in the window.
+ * This depends on nntpfs presenting contiguously
+ * numbered directories, and on the qid version being
+ * the topmost numbered directory.
+ */
+void
+dirwindow(Window *w)
+{
+ if((hi=gethi()) < 0)
+ return;
+
+ if(w->data < 0)
+ w->data = winopenfile(w, "data");
+
+ fprint(w->ctl, "dirty\n");
+
+ winopenbody(w, OWRITE);
+ lo = adddir(w->body, hi, 0, nshow);
+ winclean(w);
+}
+
+/* return 1 if handled, 0 otherwise */
+static int
+iscmd(char *s, char *cmd)
+{
+ int len;
+
+ len = strlen(cmd);
+ return strncmp(s, cmd, len)==0 && (s[len]=='\0' || s[len]==' ' || s[len]=='\t' || s[len]=='\n');
+}
+
+static char*
+skip(char *s, char *cmd)
+{
+ s += strlen(cmd);
+ while(*s==' ' || *s=='\t' || *s=='\n')
+ s++;
+ return s;
+}
+
+void
+unlink(Article *m)
+{
+ if(mlist == m)
+ mlist = m->next;
+
+ if(m->next)
+ m->next->prev = m->prev;
+ if(m->prev)
+ m->prev->next = m->next;
+ m->next = m->prev = nil;
+}
+
+int mesgopen(char*);
+int fillmesgwindow(int, Article*);
+Article *newpost(void);
+void replywindow(Article*);
+void mesgpost(Article*);
+
+/*
+ * Depends on d.qid.vers being highest numbered message in dir.
+ */
+void
+acmetimer(Article *m, Window *w)
+{
+ Biobuf *b;
+ Dir *d;
+
+ assert(m==nil && w==root);
+
+ if((d = dirstat(dir))==nil | hi==d->qid.vers){
+ free(d);
+ return;
+ }
+
+ if(w->data < 0)
+ w->data = winopenfile(w, "data");
+ if(winsetaddr(w, "0", 0))
+ write(w->data, "", 0);
+
+ b = emalloc(sizeof(*b));
+ Binit(b, w->data, OWRITE);
+ adddir(b, d->qid.vers, hi+1, d->qid.vers);
+ hi = d->qid.vers;
+ Bterm(b);
+ free(b);
+ free(d);
+ winselect(w, "0,.", 0);
+}
+
+int
+acmeload(Article*, Window*, char *s)
+{
+ int nopen;
+
+//fprint(2, "load %s\n", s);
+
+ nopen = 0;
+ while(*s){
+ /* skip directory name */
+ if(strncmp(s, dir, strlen(dir))==0)
+ s += strlen(dir);
+ nopen += mesgopen(s);
+ if((s = strchr(s, '\n')) == nil)
+ break;
+ s = skip(s, "");
+ }
+ return nopen;
+}
+
+int
+acmecmd(Article *m, Window *w, char *s)
+{
+ int n;
+ Biobuf *b;
+
+//fprint(2, "cmd %s\n", s);
+
+ s = skip(s, "");
+
+ if(iscmd(s, "Del")){
+ if(m == nil){ /* don't close dir until messages close */
+ if(mlist != nil){
+ ctlprint(mlist->w->ctl, "show\n");
+ return 1;
+ }
+ if(windel(w, 0))
+ threadexitsall(nil);
+ return 1;
+ }else{
+ if(windel(w, 0))
+ m->dead = 1;
+ return 1;
+ }
+ }
+ if(m==nil && iscmd(s, "More")){
+ s = skip(s, "More");
+ if(n = atoi(s))
+ nshow = n;
+
+ if(w->data < 0)
+ w->data = winopenfile(w, "data");
+ winsetaddr(w, "$", 1);
+
+ fprint(w->ctl, "dirty\n");
+
+ b = emalloc(sizeof(*b));
+ Binit(b, w->data, OWRITE);
+ lo = adddir(b, lo, 0, nshow);
+ Bterm(b);
+ free(b);
+ winclean(w);
+ winsetaddr(w, ".,", 0);
+ }
+ if(m!=nil && !m->ispost && iscmd(s, "Headers")){
+ m->headers = !m->headers;
+ fillmesgwindow(-1, m);
+ return 1;
+ }
+ if(iscmd(s, "Newpost")){
+ m = newpost();
+ winopenbody(m->w, OWRITE);
+ Bprint(m->w->body, "%s\nsubject: \n\n", group);
+ winclean(m->w);
+ winselect(m->w, "$", 0);
+ return 1;
+ }
+ if(m!=nil && !m->ispost && iscmd(s, "Reply")){
+ replywindow(m);
+ return 1;
+ }
+// if(m!=nil && iscmd(s, "Replymail")){
+// fprint(2, "no replymail yet\n");
+// return 1;
+// }
+ if(iscmd(s, "Post")){
+ mesgpost(m);
+ return 1;
+ }
+ return 0;
+}
+
+void
+acmeevent(Article *m, Window *w, Event *e)
+{
+ Event *ea, *e2, *eq;
+ char *s, *t, *buf;
+ int na;
+ //int n;
+ //ulong q0, q1;
+
+ switch(e->c1){ /* origin of action */
+ default:
+ Unknown:
+ fprint(2, "unknown message %c%c\n", e->c1, e->c2);
+ break;
+
+ case 'T': /* bogus timer event! */
+ acmetimer(m, w);
+ break;
+
+ case 'F': /* generated by our actions; ignore */
+ break;
+
+ case 'E': /* write to body or tag; can't affect us */
+ break;
+
+ case 'K': /* type away; we don't care */
+ if(m && (e->c2 == 'I' || e->c2 == 'D')){
+ m->dirtied = 1;
+ if(!m->sayspost){
+ wintagwrite(w, "Post ", 5);
+ m->sayspost = 1;
+ }
+ }
+ break;
+
+ case 'M': /* mouse event */
+ switch(e->c2){ /* type of action */
+ case 'x': /* mouse: button 2 in tag */
+ case 'X': /* mouse: button 2 in body */
+ ea = nil;
+ //e2 = nil;
+ s = e->b;
+ if(e->flag & 2){ /* null string with non-null expansion */
+ e2 = recvp(w->cevent);
+ if(e->nb==0)
+ s = e2->b;
+ }
+ if(e->flag & 8){ /* chorded argument */
+ ea = recvp(w->cevent); /* argument */
+ na = ea->nb;
+ recvp(w->cevent); /* ignore origin */
+ }else
+ na = 0;
+
+ /* append chorded arguments */
+ if(na){
+ t = emalloc(strlen(s)+1+na+1);
+ sprint(t, "%s %s", s, ea->b);
+ s = t;
+ }
+ /* if it's a known command, do it */
+ /* if it's a long message, it can't be for us anyway */
+ // DPRINT(2, "exec: %s\n", s);
+ if(!acmecmd(m, w, s)) /* send it back */
+ winwriteevent(w, e);
+ if(na)
+ free(s);
+ break;
+
+ case 'l': /* mouse: button 3 in tag */
+ case 'L': /* mouse: button 3 in body */
+ //buf = nil;
+ eq = e;
+ if(e->flag & 2){
+ e2 = recvp(w->cevent);
+ eq = e2;
+ }
+ s = eq->b;
+ if(eq->q1>eq->q0 && eq->nb==0){
+ buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
+ winread(w, eq->q0, eq->q1, buf);
+ s = buf;
+ }
+ if(!acmeload(m, w, s))
+ winwriteevent(w, e);
+ break;
+
+ case 'i': /* mouse: text inserted in tag */
+ case 'd': /* mouse: text deleted from tag */
+ break;
+
+ case 'I': /* mouse: text inserted in body */
+ case 'D': /* mouse: text deleted from body */
+ if(m == nil)
+ break;
+
+ m->dirtied = 1;
+ if(!m->sayspost){
+ wintagwrite(w, "Post ", 5);
+ m->sayspost = 1;
+ }
+ break;
+
+ default:
+ goto Unknown;
+ }
+ }
+}
+
+void
+dirthread(void *v)
+{
+ Event *e;
+ Window *w;
+
+ w = v;
+ while(e = recvp(w->cevent))
+ acmeevent(nil, w, e);
+
+ threadexitsall(nil);
+}
+
+void
+mesgthread(void *v)
+{
+ Event *e;
+ Article *m;
+
+ m = v;
+ while(!m->dead && (e = recvp(m->w->cevent)))
+ acmeevent(m, m->w, e);
+
+//fprint(2, "msg %p exits\n", m);
+ unlink(m);
+ free(m->w);
+ free(m);
+ threadexits(nil);
+}
+
+/*
+Xref: news.research.att.com comp.os.plan9:7360
+Newsgroups: comp.os.plan9
+Path: news.research.att.com!batch0!uunet!ffx.uu.net!finch!news.mindspring.net!newsfeed.mathworks.com!fu-berlin.de!server1.netnews.ja.net!hgmp.mrc.ac.uk!pegasus.csx.cam.ac.uk!bath.ac.uk!ccsdhd
+From: Stephen Adam <saadam@bigpond.com>
+Subject: Future of Plan9
+Approved: plan9mod@bath.ac.uk
+X-Newsreader: Microsoft Outlook Express 5.00.2014.211
+X-Mimeole: Produced By Microsoft MimeOLE V5.00.2014.211
+Sender: ccsdhd@bath.ac.uk (Dennis Davis)
+Nntp-Posting-Date: Wed, 13 Dec 2000 21:28:45 EST
+NNTP-Posting-Host: 203.54.121.233
+Organization: Telstra BigPond Internet Services (http://www.bigpond.com)
+X-Date: Wed, 13 Dec 2000 20:43:37 +1000
+Lines: 12
+Message-ID: <xbIZ5.157945$e5.114349@newsfeeds.bigpond.com>
+References: <95pghu$3lf$1@news.fas.harvard.edu> <95ph36$3m9$1@news.fas.harvard.edu> <slrn980iic.u5q.mperrin@hcs.harvard.edu> <95png6$4ln$1@news.fas.harvard.edu> <95poqg$4rq$1@news.fas.harvard.edu> <slrn980vh8.2gb.myLastName@is07.fas.harvard.edu> <95q40h$66c$2@news.fas.harvard.edu> <95qjhu$8ke$1@news.fas.harvard.edu> <95riue$bu2$1@news.fas.harvard.edu> <95rnar$cbu$1@news.fas.harvard.edu>
+X-Msmail-Priority: Normal
+X-Trace: newsfeeds.bigpond.com 976703325 203.54.121.233 (Wed, 13 Dec 2000 21:28:45 EST)
+X-Priority: 3
+Date: Wed, 13 Dec 2000 10:49:50 GMT
+*/
+
+char *skipheader[] =
+{
+ "x-",
+ "path:",
+ "xref:",
+ "approved:",
+ "sender:",
+ "nntp-",
+ "organization:",
+ "lines:",
+ "message-id:",
+ "references:",
+ "reply-to:",
+ "mime-",
+ "content-",
+};
+
+int
+fillmesgwindow(int fd, Article *m)
+{
+ Biobuf *b;
+ char *p, tmp[40];
+ int i, inhdr, copy, xfd;
+ Window *w;
+
+ xfd = -1;
+ if(fd == -1){
+ sprint(tmp, "%d/article", m->n);
+ p = estrstrdup(dir, tmp);
+ if((xfd = open(p, OREAD)) < 0){
+ free(p);
+ return 0;
+ }
+ free(p);
+ fd = xfd;
+ }
+
+ w = m->w;
+ if(w->data < 0)
+ w->data = winopenfile(w, "data");
+ if(winsetaddr(w, ",", 0))
+ write(w->data, "", 0);
+
+ winopenbody(m->w, OWRITE);
+ b = emalloc(sizeof(*b));
+ Binit(b, fd, OREAD);
+
+ inhdr = 1;
+ copy = 1;
+ while(p = Brdline(b, '\n')){
+ if(Blinelen(b)==1)
+ inhdr = 0, copy=1;
+ if(inhdr && !isspace(p[0])){
+ copy = 1;
+ if(!m->headers){
+ if(cistrncmp(p, "from:", 5)==0){
+ p[Blinelen(b)-1] = '\0';
+ p = fixfrom(skip(p, "from:"));
+ Bprint(m->w->body, "From: %s\n", p);
+ free(p);
+ copy = 0;
+ continue;
+ }
+ for(i=0; i<nelem(skipheader); i++)
+ if(cistrncmp(p, skipheader[i], strlen(skipheader[i]))==0)
+ copy=0;
+ }
+ }
+ if(copy)
+ Bwrite(m->w->body, p, Blinelen(b));
+ }
+ Bterm(b);
+ free(b);
+ winclean(m->w);
+ if(xfd != -1)
+ close(xfd);
+ return 1;
+}
+
+Article*
+newpost(void)
+{
+ Article *m;
+ char *p, tmp[40];
+ static int nnew;
+
+ m = emalloc(sizeof *m);
+ sprint(tmp, "Post%d", ++nnew);
+ p = estrstrdup(dir, tmp);
+
+ m->w = newwindow();
+ proccreate(wineventproc, m->w, STACK);
+ winname(m->w, p);
+ wintagwrite(m->w, "Post ", 5);
+ m->sayspost = 1;
+ m->ispost = 1;
+ threadcreate(mesgthread, m, STACK);
+
+ if(mlist){
+ m->next = mlist;
+ mlist->prev = m;
+ }
+ mlist = m;
+ return m;
+}
+
+void
+replywindow(Article *m)
+{
+ Biobuf *b;
+ char *p, *ep, *q, tmp[40];
+ int fd, copy;
+ Article *reply;
+
+ sprint(tmp, "%d/article", m->n);
+ p = estrstrdup(dir, tmp);
+ if((fd = open(p, OREAD)) < 0){
+ free(p);
+ return;
+ }
+ free(p);
+
+ reply = newpost();
+ winopenbody(reply->w, OWRITE);
+ b = emalloc(sizeof(*b));
+ Binit(b, fd, OREAD);
+ copy = 0;
+ while(p = Brdline(b, '\n')){
+ if(Blinelen(b)==1)
+ break;
+ ep = p+Blinelen(b);
+ if(!isspace(*p)){
+ copy = 0;
+ if(cistrncmp(p, "newsgroups:", 11)==0){
+ for(q=p+11; *q!='\n'; q++)
+ if(*q==',')
+ *q = ' ';
+ copy = 1;
+ }else if(cistrncmp(p, "subject:", 8)==0){
+ if(!strstr(p, " Re:") && !strstr(p, " RE:") && !strstr(p, " re:")){
+ p = skip(p, "subject:");
+ ep[-1] = '\0';
+ Bprint(reply->w->body, "Subject: Re: %s\n", p);
+ }else
+ copy = 1;
+ }else if(cistrncmp(p, "message-id:", 11)==0){
+ Bprint(reply->w->body, "References: ");
+ p += 11;
+ copy = 1;
+ }
+ }
+ if(copy)
+ Bwrite(reply->w->body, p, ep-p);
+ }
+ Bterm(b);
+ close(fd);
+ free(b);
+ Bprint(reply->w->body, "\n");
+ winclean(reply->w);
+ winselect(reply->w, "$", 0);
+}
+
+char*
+skipbl(char *s, char *e)
+{
+ while(s < e){
+ if(*s!=' ' && *s!='\t' && *s!=',')
+ break;
+ s++;
+ }
+ return s;
+}
+
+char*
+findbl(char *s, char *e)
+{
+ while(s < e){
+ if(*s==' ' || *s=='\t' || *s==',')
+ break;
+ s++;
+ }
+ return s;
+}
+
+/*
+ * comma-separate possibly blank-separated strings in line; e points before newline
+ */
+void
+commas(char *s, char *e)
+{
+ char *t;
+
+ /* may have initial blanks */
+ s = skipbl(s, e);
+ while(s < e){
+ s = findbl(s, e);
+ if(s == e)
+ break;
+ t = skipbl(s, e);
+ if(t == e) /* no more words */
+ break;
+ /* patch comma */
+ *s++ = ',';
+ while(s < t)
+ *s++ = ' ';
+ }
+}
+void
+mesgpost(Article *m)
+{
+ Biobuf *b;
+ char *p, *ep;
+ int isfirst, ishdr, havegroup, havefrom;
+
+ p = estrstrdup(dir, "post");
+ if((b = Bopen(p, OWRITE)) == nil){
+ fprint(2, "cannot open %s: %r\n", p);
+ free(p);
+ return;
+ }
+ free(p);
+
+ winopenbody(m->w, OREAD);
+ ishdr = 1;
+ isfirst = 1;
+ havegroup = havefrom = 0;
+ while(p = Brdline(m->w->body, '\n')){
+ ep = p+Blinelen(m->w->body);
+ if(ishdr && p+1==ep){
+ if(!havegroup)
+ Bprint(b, "Newsgroups: %s\n", group);
+ if(!havefrom)
+ Bprint(b, "From: %s\n", from);
+ ishdr = 0;
+ }
+ if(ishdr){
+ ep[-1] = '\0';
+ if(isfirst && strchr(p, ':')==0){ /* group list */
+ commas(p, ep);
+ Bprint(b, "newsgroups: %s\n", p);
+ havegroup = 1;
+ isfirst = 0;
+ continue;
+ }
+ if(cistrncmp(p, "newsgroup:", 10)==0){
+ commas(skip(p, "newsgroup:"), ep);
+ Bprint(b, "newsgroups: %s\n", skip(p, "newsgroup:"));
+ havegroup = 1;
+ continue;
+ }
+ if(cistrncmp(p, "newsgroups:", 11)==0){
+ commas(skip(p, "newsgroups:"), ep);
+ Bprint(b, "newsgroups: %s\n", skip(p, "newsgroups:"));
+ havegroup = 1;
+ continue;
+ }
+ if(cistrncmp(p, "from:", 5)==0)
+ havefrom = 1;
+ ep[-1] = '\n';
+ }
+ Bwrite(b, p, ep-p);
+ }
+ winclosebody(m->w);
+ Bflush(b);
+ if(write(Bfildes(b), "", 0) == 0)
+ winclean(m->w);
+ else
+ fprint(2, "post: %r\n");
+ Bterm(b);
+}
+
+int
+mesgopen(char *s)
+{
+ char *p, tmp[40];
+ int fd, n;
+ Article *m;
+
+ n = atoi(s);
+ if(n==0)
+ return 0;
+
+ for(m=mlist; m; m=m->next){
+ if(m->n == n){
+ ctlprint(m->w->ctl, "show\n");
+ return 1;
+ }
+ }
+
+ sprint(tmp, "%d/article", n);
+ p = estrstrdup(dir, tmp);
+ if((fd = open(p, OREAD)) < 0){
+ free(p);
+ return 0;
+ }
+
+ m = emalloc(sizeof(*m));
+ m->w = newwindow();
+ m->n = n;
+ proccreate(wineventproc, m->w, STACK);
+ p[strlen(p)-strlen("article")] = '\0';
+ winname(m->w, p);
+ if(canpost)
+ wintagwrite(m->w, "Reply ", 6);
+ wintagwrite(m->w, "Headers ", 8);
+
+ free(p);
+ if(mlist){
+ m->next = mlist;
+ mlist->prev = m;
+ }
+ mlist = m;
+ threadcreate(mesgthread, m, STACK);
+
+ fillmesgwindow(fd, m);
+ close(fd);
+ windormant(m->w);
+ return 1;
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: News [-d /mnt/news] comp.os.plan9\n");
+ exits("usage");
+}
+
+void
+timerproc(void *v)
+{
+ Event e;
+ Window *w;
+
+ memset(&e, 0, sizeof e);
+ e.c1 = 'T';
+ w = v;
+
+ for(;;){
+ sleep(60*1000);
+ sendp(w->cevent, &e);
+ }
+}
+
+char*
+findfrom(void)
+{
+ char *p, *u;
+ Biobuf *b;
+
+ u = getuser();
+ if(u==nil)
+ return "glenda";
+
+ p = estrstrstrdup("/usr/", u, "/lib/newsfrom");
+ b = Bopen(p, OREAD);
+ free(p);
+ if(b){
+ p = Brdline(b, '\n');
+ if(p){
+ p[Blinelen(b)-1] = '\0';
+ p = estrdup(p);
+ Bterm(b);
+ return p;
+ }
+ Bterm(b);
+ }
+
+ p = estrstrstrdup("/mail/box/", u, "/headers");
+ b = Bopen(p, OREAD);
+ free(p);
+ if(b){
+ while(p = Brdline(b, '\n')){
+ p[Blinelen(b)-1] = '\0';
+ if(cistrncmp(p, "from:", 5)==0){
+ p = estrdup(skip(p, "from:"));
+ Bterm(b);
+ return p;
+ }
+ }
+ Bterm(b);
+ }
+
+ return u;
+}
+
+void
+threadmain(int argc, char **argv)
+{
+ char *p, *q;
+ Dir *d;
+ Window *w;
+
+ ARGBEGIN{
+ case 'D':
+ debug++;
+ break;
+ case 'd':
+ dir = EARGF(usage());
+ break;
+ default:
+ usage();
+ break;
+ }ARGEND
+
+ if(argc != 1)
+ usage();
+
+ from = findfrom();
+
+ group = estrdup(argv[0]); /* someone will be cute */
+ while(q=strchr(group, '/'))
+ *q = '.';
+
+ p = estrdup(argv[0]);
+ while(q=strchr(p, '.'))
+ *q = '/';
+ p = estrstrstrdup(dir, "/", p);
+ cleanname(p);
+
+ if((d = dirstat(p)) == nil){ /* maybe it is a new group */
+ if((d = dirstat(dir)) == nil){
+ fprint(2, "dirstat(%s) fails: %r\n", dir);
+ threadexitsall(nil);
+ }
+ if((d->mode&DMDIR)==0){
+ fprint(2, "%s not a directory\n", dir);
+ threadexitsall(nil);
+ }
+ free(d);
+ if((d = dirstat(p)) == nil){
+ fprint(2, "stat %s: %r\n", p);
+ threadexitsall(nil);
+ }
+ }
+ if((d->mode&DMDIR)==0){
+ fprint(2, "%s not a directory\n", dir);
+ threadexitsall(nil);
+ }
+ free(d);
+ dir = estrstrdup(p, "/");
+
+ q = estrstrdup(dir, "post");
+ canpost = access(q, AWRITE)==0;
+
+ w = newwindow();
+ root = w;
+ proccreate(wineventproc, w, STACK);
+ proccreate(timerproc, w, STACK);
+
+ winname(w, dir);
+ if(canpost)
+ wintagwrite(w, "Newpost ", 8);
+ wintagwrite(w, "More ", 5);
+ dirwindow(w);
+ threadcreate(dirthread, w, STACK);
+ threadexits(nil);
+}
--- /dev/null
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include "win.h"
+
+void*
+emalloc(uint n)
+{
+ void *p;
+
+ p = malloc(n);
+ if(p == nil)
+ error("can't malloc: %r");
+ memset(p, 0, n);
+ return p;
+}
+
+char*
+estrdup(char *s)
+{
+ char *t;
+
+ t = emalloc(strlen(s)+1);
+ strcpy(t, s);
+ return t;
+}
+
+char*
+estrstrdup(char *s, char *t)
+{
+ char *u;
+
+ u = emalloc(strlen(s)+strlen(t)+1);
+ strcpy(u, s);
+ strcat(u, t);
+ return u;
+}
+
+char*
+estrstrstrdup(char *r, char *s, char *t)
+{
+ char *u;
+
+ u = emalloc(strlen(r)+strlen(s)+strlen(t)+1);
+ strcpy(u, r);
+ strcat(u, s);
+ strcat(u, t);
+ return u;
+}
+
+char*
+eappend(char *s, char *sep, char *t)
+{
+ char *u;
+
+ if(t == nil)
+ u = estrstrdup(s, sep);
+ else{
+ u = emalloc(strlen(s)+strlen(sep)+strlen(t)+1);
+ strcpy(u, s);
+ strcat(u, sep);
+ strcat(u, t);
+ }
+ free(s);
+ return u;
+}
+
+char*
+egrow(char *s, char *sep, char *t)
+{
+ s = eappend(s, sep, t);
+ free(t);
+ return s;
+}
+
+void
+error(char *fmt, ...)
+{
+ va_list arg;
+ char buf[256];
+ Fmt f;
+
+ fmtfdinit(&f, 2, buf, sizeof buf);
+ fmtprint(&f, "%s: ", argv0);
+ va_start(arg, fmt);
+ fmtprint(&f, fmt, arg);
+ va_end(arg);
+ fmtprint(&f, "\n");
+ fmtfdflush(&f);
+ threadexitsall(fmt);
+}
+
+void
+ctlprint(int fd, char *fmt, ...)
+{
+ int n;
+ va_list arg;
+ char buf[256];
+
+ va_start(arg, fmt);
+ n = vfprint(fd, fmt, arg);
+ va_end(arg);
+ if(n < 0)
+ error("control file write(%s) error: %r", buf);
+}
--- /dev/null
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include "win.h"
+
+Window*
+newwindow(void)
+{
+ char buf[12];
+ Window *w;
+
+ w = emalloc(sizeof(Window));
+ w->ctl = open("/mnt/wsys/new/ctl", ORDWR|OCEXEC);
+ if(w->ctl<0 || read(w->ctl, buf, 12)!=12)
+ error("can't open window ctl file: %r");
+ ctlprint(w->ctl, "noscroll\n");
+ w->id = atoi(buf);
+ w->event = winopenfile(w, "event");
+ w->addr = -1; /* will be opened when needed */
+ w->body = nil;
+ w->data = -1;
+ w->cevent = chancreate(sizeof(Event*), 0);
+ if(w->cevent == nil)
+ error("cevent is nil: %r");
+ return w;
+}
+
+void
+winsetdump(Window *w, char *dir, char *cmd)
+{
+ if(dir != nil)
+ ctlprint(w->ctl, "dumpdir %s\n", dir);
+ if(cmd != nil)
+ ctlprint(w->ctl, "dump %s\n", cmd);
+}
+
+void
+wineventproc(void *v)
+{
+ Window *w;
+ int i;
+
+ threadsetname("wineventproc");
+ w = v;
+ for(i=0; ; i++){
+ if(i >= NEVENT)
+ i = 0;
+ wingetevent(w, &w->e[i]);
+ sendp(w->cevent, &w->e[i]);
+ }
+}
+
+int
+winopenfile(Window *w, char *f)
+{
+ char buf[64];
+ int fd;
+
+ sprint(buf, "/mnt/wsys/%d/%s", w->id, f);
+ fd = open(buf, ORDWR|OCEXEC);
+ if(fd < 0)
+ error("can't open window file %s: %r", f);
+ return fd;
+}
+
+void
+wintagwrite(Window *w, char *s, int n)
+{
+ int fd;
+
+ fd = winopenfile(w, "tag");
+ if(write(fd, s, n) != n)
+ error("tag write: %r");
+ close(fd);
+}
+
+void
+winname(Window *w, char *s)
+{
+ ctlprint(w->ctl, "name %s\n", s);
+}
+
+void
+winopenbody(Window *w, int mode)
+{
+ char buf[256];
+
+ sprint(buf, "/mnt/wsys/%d/body", w->id);
+ w->body = Bopen(buf, mode|OCEXEC);
+ if(w->body == nil)
+ error("can't open window body file: %r");
+}
+
+void
+winclosebody(Window *w)
+{
+ if(w->body != nil){
+ Bterm(w->body);
+ w->body = nil;
+ }
+}
+
+void
+winwritebody(Window *w, char *s, int n)
+{
+ if(w->body == nil)
+ winopenbody(w, OWRITE);
+ if(Bwrite(w->body, s, n) != n)
+ error("write error to window: %r");
+}
+
+int
+wingetec(Window *w)
+{
+ if(w->nbuf == 0){
+ w->nbuf = read(w->event, w->buf, sizeof w->buf);
+ if(w->nbuf <= 0){
+ /* probably because window has exited, and only called by wineventproc, so just shut down */
+ threadexits(nil);
+ }
+ w->bufp = w->buf;
+ }
+ w->nbuf--;
+ return *w->bufp++;
+}
+
+int
+wingeten(Window *w)
+{
+ int n, c;
+
+ n = 0;
+ while('0'<=(c=wingetec(w)) && c<='9')
+ n = n*10+(c-'0');
+ if(c != ' ')
+ error("event number syntax");
+ return n;
+}
+
+int
+wingeter(Window *w, char *buf, int *nb)
+{
+ Rune r;
+ int n;
+
+ r = wingetec(w);
+ buf[0] = r;
+ n = 1;
+ if(r >= Runeself) {
+ while(!fullrune(buf, n))
+ buf[n++] = wingetec(w);
+ chartorune(&r, buf);
+ }
+ *nb = n;
+ return r;
+}
+
+void
+wingetevent(Window *w, Event *e)
+{
+ int i, nb;
+
+ e->c1 = wingetec(w);
+ e->c2 = wingetec(w);
+ e->q0 = wingeten(w);
+ e->q1 = wingeten(w);
+ e->flag = wingeten(w);
+ e->nr = wingeten(w);
+ if(e->nr > EVENTSIZE)
+ error("event string too long");
+ e->nb = 0;
+ for(i=0; i<e->nr; i++){
+ e->r[i] = wingeter(w, e->b+e->nb, &nb);
+ e->nb += nb;
+ }
+ e->r[e->nr] = 0;
+ e->b[e->nb] = 0;
+ if(wingetec(w) != '\n')
+ error("event syntax error");
+}
+
+void
+winwriteevent(Window *w, Event *e)
+{
+ fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
+}
+
+static int
+nrunes(char *s, int nb)
+{
+ int i, n;
+ Rune r;
+
+ n = 0;
+ for(i=0; i<nb; n++)
+ i += chartorune(&r, s+i);
+ return n;
+}
+
+void
+winread(Window *w, uint q0, uint q1, char *data)
+{
+ int m, n, nr;
+ char buf[256];
+
+ if(w->addr < 0)
+ w->addr = winopenfile(w, "addr");
+ if(w->data < 0)
+ w->data = winopenfile(w, "data");
+ m = q0;
+ while(m < q1){
+ n = sprint(buf, "#%d", m);
+ if(write(w->addr, buf, n) != n)
+ error("error writing addr: %r");
+ n = read(w->data, buf, sizeof buf);
+ if(n <= 0)
+ error("reading data: %r");
+ nr = nrunes(buf, n);
+ while(m+nr >q1){
+ do; while(n>0 && (buf[--n]&0xC0)==0x80);
+ --nr;
+ }
+ if(n == 0)
+ break;
+ memmove(data, buf, n);
+ data += n;
+ *data = 0;
+ m += nr;
+ }
+}
+
+void
+windormant(Window *w)
+{
+ if(w->addr >= 0){
+ close(w->addr);
+ w->addr = -1;
+ }
+ if(w->body != nil){
+ Bterm(w->body);
+ w->body = nil;
+ }
+ if(w->data >= 0){
+ close(w->data);
+ w->data = -1;
+ }
+}
+
+
+int
+windel(Window *w, int sure)
+{
+ if(sure)
+ write(w->ctl, "delete\n", 7);
+ else if(write(w->ctl, "del\n", 4) != 4)
+ return 0;
+ /* event proc will die due to read error from event file */
+ windormant(w);
+ close(w->ctl);
+ w->ctl = -1;
+ close(w->event);
+ w->event = -1;
+ return 1;
+}
+
+void
+winclean(Window *w)
+{
+ if(w->body)
+ Bflush(w->body);
+ ctlprint(w->ctl, "clean\n");
+}
+
+int
+winsetaddr(Window *w, char *addr, int errok)
+{
+ if(w->addr < 0)
+ w->addr = winopenfile(w, "addr");
+ if(write(w->addr, addr, strlen(addr)) < 0){
+ if(!errok)
+ error("error writing addr(%s): %r", addr);
+ return 0;
+ }
+ return 1;
+}
+
+int
+winselect(Window *w, char *addr, int errok)
+{
+ if(winsetaddr(w, addr, errok)){
+ ctlprint(w->ctl, "dot=addr\n");
+ return 1;
+ }
+ return 0;
+}
+
+char*
+winreadbody(Window *w, int *np) /* can't use readfile because acme doesn't report the length */
+{
+ char *s;
+ int m, na, n;
+
+ if(w->body != nil)
+ winclosebody(w);
+ winopenbody(w, OREAD);
+ s = nil;
+ na = 0;
+ n = 0;
+ for(;;){
+ if(na < n+512){
+ na += 1024;
+ s = realloc(s, na+1);
+ }
+ m = Bread(w->body, s+n, na-n);
+ if(m <= 0)
+ break;
+ n += m;
+ }
+ s[n] = 0;
+ winclosebody(w);
+ *np = n;
+ return s;
+}
--- /dev/null
+/* acme */
+typedef struct Event Event;
+typedef struct Window Window;
+
+enum
+{
+ STACK = 8192,
+ EVENTSIZE = 256,
+ NEVENT = 5,
+};
+
+struct Event
+{
+ int c1;
+ int c2;
+ int q0;
+ int q1;
+ int flag;
+ int nb;
+ int nr;
+ char b[EVENTSIZE*UTFmax+1];
+ Rune r[EVENTSIZE+1];
+};
+
+struct Window
+{
+ /* file descriptors */
+ int ctl;
+ int event;
+ int addr;
+ int data;
+ Biobuf *body;
+
+ /* event input */
+ char buf[512];
+ char *bufp;
+ int nbuf;
+ Event e[NEVENT];
+
+ int dirtied;
+ int id;
+ int open;
+ Channel *cevent; /* chan(Event*) */
+};
+
+extern Window* newwindow(void);
+extern int winopenfile(Window*, char*);
+extern void winopenbody(Window*, int);
+extern void winclosebody(Window*);
+extern void wintagwrite(Window*, char*, int);
+extern void winname(Window*, char*);
+extern void winwriteevent(Window*, Event*);
+extern void winread(Window*, uint, uint, char*);
+extern int windel(Window*, int);
+extern void wingetevent(Window*, Event*);
+extern void wineventproc(void*);
+extern void winwritebody(Window*, char*, int);
+extern void winclean(Window*);
+extern int winselect(Window*, char*, int);
+extern int winsetaddr(Window*, char*, int);
+extern char* winreadbody(Window*, int*);
+extern void windormant(Window*);
+extern void winsetdump(Window*, char*, char*);
+
+extern char* readfile(char*, char*, int*);
+extern void ctlprint(int, char*, ...);
+extern void* emalloc(uint);
+extern char* estrdup(char*);
+extern char* estrstrdup(char*, char*);
+extern char* estrstrstrdup(char*, char*, char*);
+extern char* egrow(char*, char*, char*);
+extern char* eappend(char*, char*, char*);
+extern void error(char*, ...);
+extern int tokenizec(char*, char**, int, char*);
+
--- /dev/null
+Local 9fs wiki
+# Local wikifs /sys/lib/wiki
+Wiki /mnt/wiki
--- /dev/null
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+
+/* acme */
+typedef struct Event Event;
+typedef struct Window Window;
+
+enum
+{
+ STACK = 8192,
+ EVENTSIZE = 256,
+ NEVENT = 5,
+};
+
+struct Event
+{
+ int c1;
+ int c2;
+ int q0;
+ int q1;
+ int flag;
+ int nb;
+ int nr;
+ char b[EVENTSIZE*UTFmax+1];
+ Rune r[EVENTSIZE+1];
+};
+
+struct Window
+{
+ /* file descriptors */
+ int ctl;
+ int event;
+ int addr;
+ int data;
+ Biobuf *body;
+
+ /* event input */
+ char buf[512];
+ char *bufp;
+ int nbuf;
+ Event e[NEVENT];
+
+ int warned;
+ int id;
+ int open;
+ Channel *cevent; /* chan(Event*) */
+};
+
+extern Window* newwindow(void);
+extern int winopenfile(Window*, char*);
+extern void winopenbody(Window*, int);
+extern void winclosebody(Window*);
+extern void wintagwrite(Window*, char*, int);
+extern void winname(Window*, char*);
+extern void winwriteevent(Window*, Event*);
+extern void winread(Window*, uint, uint, char*);
+extern int windel(Window*, int);
+extern void wingetevent(Window*, Event*);
+extern void wineventproc(void*);
+extern void winwritebody(Window*, char*, int);
+extern void winclean(Window*);
+extern int winisdirty(Window*);
+extern int winselect(Window*, char*, int);
+extern int winsetaddr(Window*, char*, int);
+extern char* winreadbody(Window*, int*);
+extern void windormant(Window*);
+extern void winsetdump(Window*, char*, char*);
+
+extern char* readfile(char*, char*, int*);
+extern void ctlprint(int, char*, ...);
+extern void* emalloc(uint);
+extern char* estrdup(char*);
+extern char* estrstrdup(char*, char*);
+extern char* egrow(char*, char*, char*);
+extern char* eappend(char*, char*, char*);
+extern void error(char*, ...);
+extern int tokenizec(char*, char**, int, char*);
+
+typedef struct Treq Treq;
+typedef struct Wiki Wiki;
+
+struct Treq {
+ char *title;
+ Channel *c; /* chan(int) */
+};
+
+struct Wiki {
+ QLock;
+ int isnew;
+ int special;
+ char *arg;
+ char *addr;
+ int n;
+ int dead;
+ Window *win;
+ ulong time;
+ int linked;
+ Wiki *next;
+ Wiki *prev;
+};
+
+extern int debug;
+extern int mapfd;
+extern char *email;
+extern char *dir;
+
+void wikinew(char*);
+int wikiopen(char*, char*);
+int wikiput(Wiki*);
+void wikiget(Wiki*);
+int wikidiff(Wiki*);
+
--- /dev/null
+#include "awiki.h"
+
+int debug;
+int mapfd;
+char *email;
+char *dir;
+
+void
+usage(void)
+{
+ fprint(2, "usage: Wiki [-e email] [dir]\n");
+ exits("usage");
+}
+
+void
+threadmain(int argc, char **argv)
+{
+ char *s;
+ Dir *d;
+
+ rfork(RFNAMEG);
+ ARGBEGIN{
+ case 'D':
+ debug++;
+ break;
+ case 'e':
+ email = EARGF(usage());
+ break;
+ default:
+ usage();
+ break;
+ }ARGEND
+
+ if(argc > 1)
+ usage();
+ if(argc == 1)
+ dir = argv[0];
+ else
+ dir = "/mnt/wiki";
+
+ if(chdir(dir) < 0){
+ fprint(2, "chdir(%s) fails: %r\n", dir);
+ threadexitsall(nil);
+ }
+
+ if((mapfd = open("map", ORDWR)) < 0){
+ fprint(2, "open(map): %r\n");
+ threadexitsall(nil);
+ }
+
+ if((d = dirstat("1")) == nil){
+ fprint(2, "dirstat(%s/1) fails: %r\n", dir);
+ threadexitsall(nil);
+ }
+ s = emalloc(strlen(d->name)+2);
+ strcpy(s, d->name);
+ strcat(s, "/");
+ wikiopen(s, nil);
+ threadexits(nil);
+}
--- /dev/null
+</$objtype/mkfile
+
+TARG=Wiki
+
+OFILES=\
+ main.$O\
+ util.$O\
+ wiki.$O\
+ win.$O\
+
+HFILES=awiki.h
+BIN=../../bin/$objtype
+
+</sys/src/cmd/mkone
--- /dev/null
+#include "awiki.h"
+
+void*
+emalloc(uint n)
+{
+ void *p;
+
+ p = malloc(n);
+ if(p == nil)
+ error("can't malloc: %r");
+ memset(p, 0, n);
+ return p;
+}
+
+char*
+estrdup(char *s)
+{
+ char *t;
+
+ t = emalloc(strlen(s)+1);
+ strcpy(t, s);
+ return t;
+}
+
+char*
+estrstrdup(char *s, char *t)
+{
+ char *u;
+
+ u = emalloc(strlen(s)+strlen(t)+1);
+ strcpy(u, s);
+ strcat(u, t);
+ return u;
+}
+
+char*
+eappend(char *s, char *sep, char *t)
+{
+ char *u;
+
+ if(t == nil)
+ u = estrstrdup(s, sep);
+ else{
+ u = emalloc(strlen(s)+strlen(sep)+strlen(t)+1);
+ strcpy(u, s);
+ strcat(u, sep);
+ strcat(u, t);
+ }
+ free(s);
+ return u;
+}
+
+char*
+egrow(char *s, char *sep, char *t)
+{
+ s = eappend(s, sep, t);
+ free(t);
+ return s;
+}
+
+void
+error(char *fmt, ...)
+{
+ int n;
+ va_list arg;
+ char buf[256];
+
+ fprint(2, "Wiki: ");
+ va_start(arg, fmt);
+ n = vseprint(buf, buf+sizeof buf, fmt, arg) - buf;
+ va_end(arg);
+ write(2, buf, n);
+ write(2, "\n", 1);
+ threadexitsall(fmt);
+}
+
+void
+ctlprint(int fd, char *fmt, ...)
+{
+ int n;
+ va_list arg;
+ char buf[256];
+
+ va_start(arg, fmt);
+ n = vseprint(buf, buf+sizeof buf, fmt, arg) - buf;
+ va_end(arg);
+ if(write(fd, buf, n) != n)
+ error("control file write(%s) error: %r", buf);
+}
--- /dev/null
+#include "awiki.h"
+
+Wiki *wlist;
+
+void
+link(Wiki *w)
+{
+ if(w->linked)
+ return;
+ w->linked = 1;
+ w->prev = nil;
+ w->next = wlist;
+ if(wlist)
+ wlist->prev = w;
+ wlist = w;
+}
+
+void
+unlink(Wiki *w)
+{
+ if(!w->linked)
+ return;
+ w->linked = 0;
+
+ if(w->next)
+ w->next->prev = w->prev;
+ if(w->prev)
+ w->prev->next = w->next;
+ else
+ wlist = w->next;
+
+ w->next = nil;
+ w->prev = nil;
+}
+
+void
+wikiname(Window *w, char *name)
+{
+ char *p, *q;
+
+ p = emalloc(strlen(dir)+1+strlen(name)+1+1);
+ strcpy(p, dir);
+ strcat(p, "/");
+ strcat(p, name);
+ for(q=p; *q; q++)
+ if(*q==' ')
+ *q = '_';
+ winname(w, p);
+ free(p);
+}
+
+int
+wikiput(Wiki *w)
+{
+ int fd, n;
+ char buf[1024], *p;
+ Biobuf *b;
+
+ if((fd = open("new", ORDWR)) < 0){
+ fprint(2, "Wiki: cannot open raw: %r\n");
+ return -1;
+ }
+
+ winopenbody(w->win, OREAD);
+ b = w->win->body;
+ if((p = Brdline(b, '\n'))==nil){
+ Short:
+ winclosebody(w->win);
+ fprint(2, "Wiki: no data\n");
+ close(fd);
+ return -1;
+ }
+ write(fd, p, Blinelen(b));
+
+ snprint(buf, sizeof buf, "D%lud\n", w->time);
+ if(email)
+ snprint(buf+strlen(buf), sizeof(buf)-strlen(buf), "A%s\n", email);
+
+ if(Bgetc(b) == '#'){
+ p = Brdline(b, '\n');
+ if(p == nil)
+ goto Short;
+ snprint(buf+strlen(buf), sizeof(buf)-strlen(buf), "C%s\n", p);
+ }
+ write(fd, buf, strlen(buf));
+ write(fd, "\n\n", 2);
+
+ while((n = Bread(b, buf, sizeof buf)) > 0)
+ write(fd, buf, n);
+ winclosebody(w->win);
+
+ werrstr("");
+ if((n=write(fd, "", 0)) != 0){
+ fprint(2, "Wiki commit %lud %d %d: %r\n", w->time, fd, n);
+ close(fd);
+ return -1;
+ }
+ seek(fd, 0, 0);
+ if((n = read(fd, buf, 300)) < 0){
+ fprint(2, "Wiki readback: %r\n");
+ close(fd);
+ return -1;
+ }
+ close(fd);
+ buf[n] = '\0';
+ sprint(buf, "%s/", buf);
+ free(w->arg);
+ w->arg = estrdup(buf);
+ w->isnew = 0;
+ wikiget(w);
+ wikiname(w->win, w->arg);
+ return n;
+}
+
+void
+wikiget(Wiki *w)
+{
+ char *p;
+ int fd, normal;
+ Biobuf *bin;
+
+ fprint(w->win->ctl, "dirty\n");
+
+ p = emalloc(strlen(w->arg)+8+1);
+ strcpy(p, w->arg);
+ normal = 1;
+ if(p[strlen(p)-1] == '/'){
+ normal = 0;
+ strcat(p, "current");
+ }else if(strlen(p)>8 && strcmp(p+strlen(p)-8, "/current")==0){
+ normal = 0;
+ w->arg[strlen(w->arg)-7] = '\0';
+ }
+
+ if((fd = open(p, OREAD)) < 0){
+ fprint(2, "Wiki: cannot read %s: %r\n", p);
+ winclean(w->win);
+ return;
+ }
+ free(p);
+
+ winopenbody(w->win, OWRITE);
+ bin = emalloc(sizeof(*bin));
+ Binit(bin, fd, OREAD);
+
+ p = nil;
+ if(!normal){
+ if((p = Brdline(bin, '\n')) == nil){
+ fprint(2, "Wiki: cannot read title: %r\n");
+ winclean(w->win);
+ close(fd);
+ free(bin);
+ return;
+ }
+ p[Blinelen(bin)-1] = '\0';
+ }
+ /* clear window */
+ if(w->win->data < 0)
+ w->win->data = winopenfile(w->win, "data");
+ if(winsetaddr(w->win, ",", 0))
+ write(w->win->data, "", 0);
+
+ if(!normal)
+ Bprint(w->win->body, "%s\n\n", p);
+
+ while(p = Brdline(bin, '\n')){
+ p[Blinelen(bin)-1] = '\0';
+ if(normal)
+ Bprint(w->win->body, "%s\n", p);
+ else{
+ if(p[0]=='D')
+ w->time = strtoul(p+1, 0, 10);
+ else if(p[0]=='#')
+ Bprint(w->win->body, "%s\n", p+1);
+ }
+ }
+ winclean(w->win);
+ free(bin);
+ close(fd);
+}
+
+static int
+iscmd(char *s, char *cmd)
+{
+ int len;
+
+ len = strlen(cmd);
+ return strncmp(s, cmd, len)==0 && (s[len]=='\0' || s[len]==' ' || s[len]=='\t' || s[len]=='\n');
+}
+
+static char*
+skip(char *s, char *cmd)
+{
+ s += strlen(cmd);
+ while(*s==' ' || *s=='\t' || *s=='\n')
+ s++;
+ return s;
+}
+
+int
+wikiload(Wiki *w, char *arg)
+{
+ char *p, *q, *path, *addr;
+ int rv;
+
+ p = nil;
+ if(arg[0] == '/')
+ path = arg;
+ else{
+ p = emalloc(strlen(w->arg)+1+strlen(arg)+1);
+ strcpy(p, w->arg);
+ if(q = strrchr(p, '/')){
+ ++q;
+ *q = '\0';
+ }else
+ *p = '\0';
+ strcat(p, arg);
+ cleanname(p);
+ path = p;
+ }
+ if(addr=strchr(path, ':'))
+ *addr++ = '\0';
+
+ rv = wikiopen(path, addr)==0;
+ free(p);
+ if(rv)
+ return 1;
+ return wikiopen(arg, 0)==0;
+}
+
+/* return 1 if handled, 0 otherwise */
+int
+wikicmd(Wiki *w, char *s)
+{
+ char *p;
+ s = skip(s, "");
+
+ if(iscmd(s, "Del")){
+ if(windel(w->win, 0))
+ w->dead = 1;
+ return 1;
+ }
+ if(iscmd(s, "New")){
+ wikinew(skip(s, "New"));
+ return 1;
+ }
+ if(iscmd(s, "History"))
+ return wikiload(w, "history.txt");
+ if(iscmd(s, "Diff"))
+ return wikidiff(w);
+ if(iscmd(s, "Get")){
+ if(winisdirty(w->win) && !w->win->warned){
+ w->win->warned = 1;
+ fprint(2, "%s/%s modified\n", dir, w->arg);
+ }else{
+ w->win->warned = 0;
+ wikiget(w);
+ }
+ return 1;
+ }
+ if(iscmd(s, "Put")){
+ if((p=strchr(w->arg, '/')) && p[1]!='\0')
+ fprint(2, "%s/%s is read-only\n", dir, w->arg);
+ else
+ wikiput(w);
+ return 1;
+ }
+ return 0;
+}
+
+/* need to expand selection more than default word */
+static long
+eval(Window *w, char *s, ...)
+{
+ char buf[64];
+ va_list arg;
+
+ va_start(arg, s);
+ vsnprint(buf, sizeof buf, s, arg);
+ va_end(arg);
+
+ if(winsetaddr(w, buf, 1)==0)
+ return -1;
+
+ if(pread(w->addr, buf, 24, 0) != 24)
+ return -1;
+ return strtol(buf, 0, 10);
+}
+
+static int
+getdot(Window *w, long *q0, long *q1)
+{
+ char buf[24];
+
+ ctlprint(w->ctl, "addr=dot\n");
+ if(pread(w->addr, buf, 24, 0) != 24)
+ return -1;
+ *q0 = atoi(buf);
+ *q1 = atoi(buf+12);
+ return 0;
+}
+
+static Event*
+expand(Window *w, Event *e, Event *eacme)
+{
+ long q0, q1, x;
+
+ if(getdot(w, &q0, &q1)==0 && q0 <= e->q0 && e->q0 <= q1){
+ e->q0 = q0;
+ e->q1 = q1;
+ return e;
+ }
+
+ q0 = eval(w, "#%lud-/\\[/", e->q0);
+ if(q0 < 0)
+ return eacme;
+ if(eval(w, "#%lud+/\\]/", q0) < e->q0) /* [ closes before us */
+ return eacme;
+ q1 = eval(w, "#%lud+/\\]/", e->q1);
+ if(q1 < 0)
+ return eacme;
+ if((x=eval(w, "#%lud-/\\[/", q1))==-1 || x > e->q1) /* ] opens after us */
+ return eacme;
+ e->q0 = q0+1;
+ e->q1 = q1;
+ return e;
+}
+
+void
+acmeevent(Wiki *wiki, Event *e)
+{
+ Event *ea, *e2, *eq;
+ Window *w;
+ char *s, *t, *buf;
+ int na;
+
+ w = wiki->win;
+ switch(e->c1){ /* origin of action */
+ default:
+ Unknown:
+ fprint(2, "unknown message %c%c\n", e->c1, e->c2);
+ break;
+
+ case 'F': /* generated by our actions; ignore */
+ break;
+
+ case 'E': /* write to body or tag; can't affect us */
+ break;
+
+ case 'K': /* type away; we don't care */
+ if(e->c2 == 'I' || e->c2 == 'D')
+ w->warned = 0;
+ break;
+
+ case 'M': /* mouse event */
+ switch(e->c2){ /* type of action */
+ case 'x': /* mouse: button 2 in tag */
+ case 'X': /* mouse: button 2 in body */
+ ea = nil;
+ //e2 = nil;
+ s = e->b;
+ if(e->flag & 2){ /* null string with non-null expansion */
+ e2 = recvp(w->cevent);
+ if(e->nb==0)
+ s = e2->b;
+ }
+ if(e->flag & 8){ /* chorded argument */
+ ea = recvp(w->cevent); /* argument */
+ na = ea->nb;
+ recvp(w->cevent); /* ignore origin */
+ }else
+ na = 0;
+
+ /* append chorded arguments */
+ if(na){
+ t = emalloc(strlen(s)+1+na+1);
+ sprint(t, "%s %s", s, ea->b);
+ s = t;
+ }
+ /* if it's a known command, do it */
+ /* if it's a long message, it can't be for us anyway */
+ // DPRINT(2, "exec: %s\n", s);
+ if(!wikicmd(wiki, s)) /* send it back */
+ winwriteevent(w, e);
+ if(na)
+ free(s);
+ break;
+
+ case 'l': /* mouse: button 3 in tag */
+ case 'L': /* mouse: button 3 in body */
+ //buf = nil;
+ eq = e;
+ if(e->flag & 2){ /* we do our own expansion for loads */
+ e2 = recvp(w->cevent);
+ eq = expand(w, eq, e2);
+ }
+ s = eq->b;
+ if(eq->q1>eq->q0 && eq->nb==0){
+ buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
+ winread(w, eq->q0, eq->q1, buf);
+ s = buf;
+ }
+ if(!wikiload(wiki, s))
+ winwriteevent(w, e);
+ break;
+
+ case 'i': /* mouse: text inserted in tag */
+ case 'd': /* mouse: text deleted from tag */
+ break;
+
+ case 'I': /* mouse: text inserted in body */
+ case 'D': /* mouse: text deleted from body */
+ w->warned = 0;
+ break;
+
+ default:
+ goto Unknown;
+ }
+ }
+}
+
+void
+wikithread(void *v)
+{
+ char tmp[40];
+ Event *e;
+ Wiki *w;
+
+ w = v;
+
+ if(w->isnew){
+ sprint(tmp, "+new+%d", w->isnew);
+ wikiname(w->win, tmp);
+ if(w->arg){
+ winopenbody(w->win, OWRITE);
+ Bprint(w->win->body, "%s\n\n", w->arg);
+ }
+ winclean(w->win);
+ }else if(!w->special){
+ wikiget(w);
+ wikiname(w->win, w->arg);
+ if(w->addr)
+ winselect(w->win, w->addr, 1);
+ }
+ fprint(w->win->ctl, "menu\n");
+ wintagwrite(w->win, "Get History Diff New", 4+8+4+4);
+ winclean(w->win);
+
+ while(!w->dead && (e = recvp(w->win->cevent)))
+ acmeevent(w, e);
+
+ windormant(w->win);
+ unlink(w);
+ free(w->win);
+ free(w->arg);
+ free(w);
+ threadexits(nil);
+}
+
+int
+wikiopen(char *arg, char *addr)
+{
+ Dir *d;
+ char *p;
+ Wiki *w;
+
+/*
+ if(arg==nil){
+ if(write(mapfd, title, strlen(title)) < 0
+ || seek(mapfd, 0, 0) < 0 || (n=read(mapfd, tmp, sizeof(tmp)-2)) < 0){
+ fprint(2, "Wiki: no page '%s' found: %r\n", title);
+ return -1;
+ }
+ if(tmp[n-1] == '\n')
+ tmp[--n] = '\0';
+ tmp[n++] = '/';
+ tmp[n] = '\0';
+ arg = tmp;
+ }
+*/
+
+ /* replace embedded '\n' in links by ' ' */
+ for(p=arg; *p; p++)
+ if(*p=='\n')
+ *p = ' ';
+
+ if(strncmp(arg, dir, strlen(dir))==0 && arg[strlen(dir)]=='/' && arg[strlen(dir)+1])
+ arg += strlen(dir)+1;
+ else if(arg[0] == '/')
+ return -1;
+
+ if((d = dirstat(arg)) == nil)
+ return -1;
+
+ if((d->mode&DMDIR) && arg[strlen(arg)-1] != '/'){
+ p = emalloc(strlen(arg)+2);
+ strcpy(p, arg);
+ strcat(p, "/");
+ arg = p;
+ }else if(!(d->mode&DMDIR) && arg[strlen(arg)-1]=='/'){
+ arg = estrdup(arg);
+ arg[strlen(arg)-1] = '\0';
+ }else
+ arg = estrdup(arg);
+ free(d);
+
+ /* rewrite /current into / */
+ if(strlen(arg) > 8 && strcmp(arg+strlen(arg)-8, "/current")==0)
+ arg[strlen(arg)-8+1] = '\0';
+
+ /* look for window already open */
+ for(w=wlist; w; w=w->next){
+ if(strcmp(w->arg, arg)==0){
+ ctlprint(w->win->ctl, "show\n");
+ return 0;
+ }
+ }
+
+ w = emalloc(sizeof *w);
+ w->arg = arg;
+ w->addr = addr;
+ w->win = newwindow();
+ link(w);
+
+ proccreate(wineventproc, w->win, STACK);
+ threadcreate(wikithread, w, STACK);
+ return 0;
+}
+
+void
+wikinew(char *arg)
+{
+ static int n;
+ Wiki *w;
+
+ w = emalloc(sizeof *w);
+ if(arg)
+ arg = estrdup(arg);
+ w->arg = arg;
+ w->win = newwindow();
+ w->isnew = ++n;
+ proccreate(wineventproc, w->win, STACK);
+ threadcreate(wikithread, w, STACK);
+}
+
+typedef struct Diffarg Diffarg;
+struct Diffarg {
+ Wiki *w;
+ char *dir;
+};
+
+void
+execdiff(void *v)
+{
+ char buf[64];
+ Diffarg *a;
+
+ a = v;
+
+ rfork(RFFDG);
+ close(0);
+ open("/dev/null", OREAD);
+ sprint(buf, "/mnt/wsys/%d/body", a->w->win->id);
+ close(1);
+ open(buf, OWRITE);
+ close(2);
+ open(buf, OWRITE);
+ sprint(buf, "/mnt/wsys/%d", a->w->win->id);
+ bind(buf, "/dev", MBEFORE);
+
+ procexecl(nil, "/acme/wiki/wiki.diff", "wiki.diff", a->dir, nil);
+}
+
+int
+wikidiff(Wiki *w)
+{
+ Diffarg *d;
+ char *p, *q, *r;
+ Wiki *nw;
+
+ p = emalloc(strlen(w->arg)+10);
+ strcpy(p, w->arg);
+ if(q = strchr(p, '/'))
+ *q = '\0';
+ r = estrdup(p);
+ strcat(p, "/+Diff");
+
+ nw = emalloc(sizeof *w);
+ nw->arg = p;
+ nw->win = newwindow();
+ nw->special = 1;
+
+ d = emalloc(sizeof(*d));
+ d->w = nw;
+ d->dir = r;
+ wikiname(nw->win, p);
+ proccreate(wineventproc, nw->win, STACK);
+ proccreate(execdiff, d, STACK);
+ threadcreate(wikithread, nw, STACK);
+ return 1;
+}
+
--- /dev/null
+#include "awiki.h"
+
+Window*
+newwindow(void)
+{
+ char buf[12];
+ Window *w;
+
+ w = emalloc(sizeof(Window));
+ w->ctl = open("/mnt/wsys/new/ctl", ORDWR|OCEXEC);
+ if(w->ctl<0 || read(w->ctl, buf, 12)!=12)
+ error("can't open window ctl file: %r");
+ ctlprint(w->ctl, "noscroll\n");
+ w->id = atoi(buf);
+ w->event = winopenfile(w, "event");
+ w->addr = -1; /* will be opened when needed */
+ w->body = nil;
+ w->data = -1;
+ w->cevent = chancreate(sizeof(Event*), 0);
+ if(w->cevent == nil)
+ error("cevent is nil: %r");
+ return w;
+}
+
+void
+winsetdump(Window *w, char *dir, char *cmd)
+{
+ if(dir != nil)
+ ctlprint(w->ctl, "dumpdir %s\n", dir);
+ if(cmd != nil)
+ ctlprint(w->ctl, "dump %s\n", cmd);
+}
+
+void
+wineventproc(void *v)
+{
+ Window *w;
+ int i;
+
+ threadsetname("wineventproc");
+ w = v;
+ for(i=0; ; i++){
+ if(i >= NEVENT)
+ i = 0;
+ wingetevent(w, &w->e[i]);
+ sendp(w->cevent, &w->e[i]);
+ }
+}
+
+int
+winopenfile(Window *w, char *f)
+{
+ char buf[64];
+ int fd;
+
+ sprint(buf, "/mnt/wsys/%d/%s", w->id, f);
+ fd = open(buf, ORDWR|OCEXEC);
+ if(fd < 0)
+ error("can't open window file %s: %r", f);
+ return fd;
+}
+
+void
+wintagwrite(Window *w, char *s, int n)
+{
+ int fd;
+
+ fd = winopenfile(w, "tag");
+ if(write(fd, s, n) != n)
+ error("tag write: %r");
+ close(fd);
+}
+
+void
+winname(Window *w, char *s)
+{
+ ctlprint(w->ctl, "name %s\n", s);
+}
+
+void
+winopenbody(Window *w, int mode)
+{
+ char buf[256];
+
+ sprint(buf, "/mnt/wsys/%d/body", w->id);
+ w->body = Bopen(buf, mode|OCEXEC);
+ if(w->body == nil)
+ error("can't open window body file: %r");
+}
+
+void
+winclosebody(Window *w)
+{
+ if(w->body != nil){
+ Bterm(w->body);
+ w->body = nil;
+ }
+}
+
+void
+winwritebody(Window *w, char *s, int n)
+{
+ if(w->body == nil)
+ winopenbody(w, OWRITE);
+ if(Bwrite(w->body, s, n) != n)
+ error("write error to window: %r");
+}
+
+int
+wingetec(Window *w)
+{
+ if(w->nbuf == 0){
+ w->nbuf = read(w->event, w->buf, sizeof w->buf);
+ if(w->nbuf <= 0){
+ /* probably because window has exited, and only called by wineventproc, so just shut down */
+ threadexits(nil);
+ }
+ w->bufp = w->buf;
+ }
+ w->nbuf--;
+ return *w->bufp++;
+}
+
+int
+wingeten(Window *w)
+{
+ int n, c;
+
+ n = 0;
+ while('0'<=(c=wingetec(w)) && c<='9')
+ n = n*10+(c-'0');
+ if(c != ' ')
+ error("event number syntax");
+ return n;
+}
+
+int
+wingeter(Window *w, char *buf, int *nb)
+{
+ Rune r;
+ int n;
+
+ r = wingetec(w);
+ buf[0] = r;
+ n = 1;
+ if(r >= Runeself) {
+ while(!fullrune(buf, n))
+ buf[n++] = wingetec(w);
+ chartorune(&r, buf);
+ }
+ *nb = n;
+ return r;
+}
+
+void
+wingetevent(Window *w, Event *e)
+{
+ int i, nb;
+
+ e->c1 = wingetec(w);
+ e->c2 = wingetec(w);
+ e->q0 = wingeten(w);
+ e->q1 = wingeten(w);
+ e->flag = wingeten(w);
+ e->nr = wingeten(w);
+ if(e->nr > EVENTSIZE)
+ error("event string too long");
+ e->nb = 0;
+ for(i=0; i<e->nr; i++){
+ e->r[i] = wingeter(w, e->b+e->nb, &nb);
+ e->nb += nb;
+ }
+ e->r[e->nr] = 0;
+ e->b[e->nb] = 0;
+ if(wingetec(w) != '\n')
+ error("event syntax error");
+}
+
+void
+winwriteevent(Window *w, Event *e)
+{
+ fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
+}
+
+static int
+nrunes(char *s, int nb)
+{
+ int i, n;
+ Rune r;
+
+ n = 0;
+ for(i=0; i<nb; n++)
+ i += chartorune(&r, s+i);
+ return n;
+}
+
+void
+winread(Window *w, uint q0, uint q1, char *data)
+{
+ int m, n, nr;
+ char buf[256];
+
+ if(w->addr < 0)
+ w->addr = winopenfile(w, "addr");
+ if(w->data < 0)
+ w->data = winopenfile(w, "data");
+ m = q0;
+ while(m < q1){
+ n = sprint(buf, "#%d", m);
+ if(write(w->addr, buf, n) != n)
+ error("error writing addr: %r");
+ n = read(w->data, buf, sizeof buf);
+ if(n <= 0)
+ error("reading data: %r");
+ nr = nrunes(buf, n);
+ while(m+nr >q1){
+ do; while(n>0 && (buf[--n]&0xC0)==0x80);
+ --nr;
+ }
+ if(n == 0)
+ break;
+ memmove(data, buf, n);
+ data += n;
+ *data = 0;
+ m += nr;
+ }
+}
+
+void
+windormant(Window *w)
+{
+ if(w->addr >= 0){
+ close(w->addr);
+ w->addr = -1;
+ }
+ if(w->body != nil){
+ Bterm(w->body);
+ w->body = nil;
+ }
+ if(w->data >= 0){
+ close(w->data);
+ w->data = -1;
+ }
+}
+
+
+int
+windel(Window *w, int sure)
+{
+ if(sure)
+ write(w->ctl, "delete\n", 7);
+ else if(write(w->ctl, "del\n", 4) != 4)
+ return 0;
+ /* event proc will die due to read error from event file */
+ windormant(w);
+ close(w->ctl);
+ w->ctl = -1;
+ close(w->event);
+ w->event = -1;
+ return 1;
+}
+
+void
+winclean(Window *w)
+{
+ if(w->body)
+ Bflush(w->body);
+ ctlprint(w->ctl, "clean\n");
+}
+
+int
+winisdirty(Window *w)
+{
+ char m;
+
+ if (seek(w->ctl, 4*(11+1) + 10, 0) < 0)
+ error("control file seek error: %r");
+
+ if(read(w->ctl, &m, 1) != 1)
+ error("control file read error: %r");
+
+ if (m == '0')
+ return 0;
+ else if (m == '1')
+ return 1;
+ else
+ error("can't parse ismodified field: %c", m);
+ return 1; // better safe than sorry
+
+}
+
+int
+winsetaddr(Window *w, char *addr, int errok)
+{
+ if(w->addr < 0)
+ w->addr = winopenfile(w, "addr");
+ if(write(w->addr, addr, strlen(addr)) < 0){
+ if(!errok)
+ error("error writing addr(%s): %r", addr);
+ return 0;
+ }
+ return 1;
+}
+
+int
+winselect(Window *w, char *addr, int errok)
+{
+ if(winsetaddr(w, addr, errok)){
+ ctlprint(w->ctl, "dot=addr\n");
+ return 1;
+ }
+ return 0;
+}
+
+char*
+winreadbody(Window *w, int *np) /* can't use readfile because acme doesn't report the length */
+{
+ char *s;
+ int m, na, n;
+
+ if(w->body != nil)
+ winclosebody(w);
+ winopenbody(w, OREAD);
+ s = nil;
+ na = 0;
+ n = 0;
+ for(;;){
+ if(na < n+512){
+ na += 1024;
+ s = realloc(s, na+1);
+ }
+ m = Bread(w->body, s+n, na-n);
+ if(m <= 0)
+ break;
+ n += m;
+ }
+ s[n] = 0;
+ winclosebody(w);
+ *np = n;
+ return s;
+}
--- /dev/null
+#!/bin/rc
+
+rfork n
+cd $1
+*=(`{ls -drp [0-9]*})
+
+while(! ~ $#* 0 1){
+ diff -n $2/index.txt $1/index.txt | awk -F'[\/ :]' '
+ $1 ~/^[0-9]+$/ {
+ getA = "cat "$5"/current | sed -n -e ''1d; /^A/s/^A//p; /^#/q''"
+ getA | getline A; close getA
+ $1 = t2d($1)
+ $5 = t2d($5)
+ print "\n" A ":\n" $1":"$3" "$4" "$5":"$7
+ next
+ }
+ { print }
+
+ function t2d(t) {
+ c = "date "t; c|getline l; close c
+ split(l, a, "[ :]+")
+ return a[1]" "a[2]" "a[3]" "a[4]":"a[5]" "a[8]"("t")"
+ }'
+ shift
+}
+
+echo clean >/dev/ctl >[2]/dev/null