]> git.lizzy.rs Git - plan9front.git/commitdiff
fill /acme
authorcinap_lenrek <cinap_lenrek@localhost>
Thu, 14 Apr 2011 17:27:24 +0000 (17:27 +0000)
committercinap_lenrek <cinap_lenrek@localhost>
Thu, 14 Apr 2011 17:27:24 +0000 (17:27 +0000)
123 files changed:
386/bin/.dummy [deleted file]
386/bin/ip/.dummy [deleted file]
386/lib/.dummy [deleted file]
68000/bin/.dummy [deleted file]
68000/bin/ip/.dummy [deleted file]
68000/lib/.dummy [deleted file]
68020/bin/.dummy [deleted file]
68020/bin/ip/.dummy [deleted file]
68020/lib/.dummy [deleted file]
acme/.dummy [deleted file]
acme/acid/Acid [new file with mode: 0755]
acme/acid/guide [new file with mode: 0644]
acme/bin/386/.dummy [new file with mode: 0644]
acme/bin/Battery [new file with mode: 0755]
acme/bin/Isspam [new file with mode: 0755]
acme/bin/Mail [new file with mode: 0755]
acme/bin/Perl [new file with mode: 0755]
acme/bin/README [new file with mode: 0644]
acme/bin/Spam [new file with mode: 0755]
acme/bin/Unspam [new file with mode: 0755]
acme/bin/adiff [new file with mode: 0755]
acme/bin/agrep [new file with mode: 0755]
acme/bin/alpha/.dummy [new file with mode: 0644]
acme/bin/ap [new file with mode: 0755]
acme/bin/arm/.dummy [new file with mode: 0644]
acme/bin/aspell [new file with mode: 0755]
acme/bin/dial/.dummy [new file with mode: 0644]
acme/bin/guide [new file with mode: 0644]
acme/bin/ind [new file with mode: 0755]
acme/bin/mips/.dummy [new file with mode: 0644]
acme/bin/new [new file with mode: 0755]
acme/bin/power/.dummy [new file with mode: 0644]
acme/bin/power64/.dummy [new file with mode: 0644]
acme/bin/quote [new file with mode: 0755]
acme/bin/source/acd/README [new file with mode: 0644]
acme/bin/source/acd/access [new file with mode: 0644]
acme/bin/source/acd/acd.h [new file with mode: 0644]
acme/bin/source/acd/acme.c [new file with mode: 0644]
acme/bin/source/acd/cddb [new file with mode: 0644]
acme/bin/source/acd/cddb.c [new file with mode: 0644]
acme/bin/source/acd/cddbproto [new file with mode: 0644]
acme/bin/source/acd/discid [new file with mode: 0644]
acme/bin/source/acd/mailinglist [new file with mode: 0644]
acme/bin/source/acd/main.c [new file with mode: 0644]
acme/bin/source/acd/mkfile [new file with mode: 0644]
acme/bin/source/acd/mmc.c [new file with mode: 0644]
acme/bin/source/acd/outline [new file with mode: 0644]
acme/bin/source/acd/submit [new file with mode: 0644]
acme/bin/source/acd/toc.c [new file with mode: 0644]
acme/bin/source/acd/util.c [new file with mode: 0644]
acme/bin/source/acd/win.c [new file with mode: 0644]
acme/bin/source/adict/_adict.c [new file with mode: 0644]
acme/bin/source/adict/_win.c [new file with mode: 0644]
acme/bin/source/adict/adict.c [new file with mode: 0644]
acme/bin/source/adict/adict.h [new file with mode: 0644]
acme/bin/source/adict/man [new file with mode: 0644]
acme/bin/source/adict/mkfile [new file with mode: 0644]
acme/bin/source/adict/win.c [new file with mode: 0644]
acme/bin/source/adict/win.h [new file with mode: 0644]
acme/bin/source/mkfile [new file with mode: 0644]
acme/bin/source/mkwnew.c [new file with mode: 0644]
acme/bin/source/spout.c [new file with mode: 0644]
acme/bin/source/win/_fs.c [new file with mode: 0644]
acme/bin/source/win/_main.c [new file with mode: 0644]
acme/bin/source/win/dat.h [new file with mode: 0644]
acme/bin/source/win/fs.c [new file with mode: 0644]
acme/bin/source/win/main.c [new file with mode: 0644]
acme/bin/source/win/mkfile [new file with mode: 0644]
acme/bin/source/win/pipe.c [new file with mode: 0644]
acme/bin/source/win/util.c [new file with mode: 0644]
acme/bin/source/win/win.c [new file with mode: 0644]
acme/bin/sparc/.dummy [new file with mode: 0644]
acme/bin/sparc64/.dummy [new file with mode: 0644]
acme/bin/unind [new file with mode: 0755]
acme/bin/wnew [new file with mode: 0755]
acme/mail/guide [new file with mode: 0644]
acme/mail/mkbox [new file with mode: 0755]
acme/mail/readme [new file with mode: 0644]
acme/mail/src/dat.h [new file with mode: 0644]
acme/mail/src/html.c [new file with mode: 0644]
acme/mail/src/mail.c [new file with mode: 0644]
acme/mail/src/mesg.c [new file with mode: 0644]
acme/mail/src/mkfile [new file with mode: 0644]
acme/mail/src/reply.c [new file with mode: 0644]
acme/mail/src/util.c [new file with mode: 0644]
acme/mail/src/win.c [new file with mode: 0644]
acme/mkfile [new file with mode: 0755]
acme/news/guide [new file with mode: 0644]
acme/news/src/mkfile [new file with mode: 0644]
acme/news/src/news.c [new file with mode: 0644]
acme/news/src/util.c [new file with mode: 0644]
acme/news/src/win.c [new file with mode: 0644]
acme/news/src/win.h [new file with mode: 0644]
acme/wiki/guide [new file with mode: 0644]
acme/wiki/src/awiki.h [new file with mode: 0644]
acme/wiki/src/main.c [new file with mode: 0644]
acme/wiki/src/mkfile [new file with mode: 0644]
acme/wiki/src/util.c [new file with mode: 0644]
acme/wiki/src/wiki.c [new file with mode: 0644]
acme/wiki/src/win.c [new file with mode: 0644]
acme/wiki/wiki.diff [new file with mode: 0755]
alpha/bin/.dummy [deleted file]
alpha/bin/ip/.dummy [deleted file]
amd64/bin/.dummy [deleted file]
amd64/bin/ip/.dummy [deleted file]
amd64/lib/.dummy [deleted file]
arm/bin/.dummy [deleted file]
arm/bin/ip/.dummy [deleted file]
arm/lib/.dummy [deleted file]
mips/bin/.dummy [deleted file]
mips/lib/.dummy [deleted file]
power/bin/.dummy [deleted file]
power/bin/ip/.dummy [deleted file]
power/lib/.dummy [deleted file]
power64/bin/.dummy [deleted file]
power64/bin/ip/.dummy [deleted file]
power64/lib/.dummy [deleted file]
sparc/bin/.dummy [deleted file]
sparc/bin/ip/.dummy [deleted file]
sparc/lib/.dummy [deleted file]
sparc64/bin/.dummy [deleted file]
sparc64/bin/ip/.dummy [deleted file]
sparc64/lib/.dummy [deleted file]

diff --git a/386/bin/.dummy b/386/bin/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/386/bin/ip/.dummy b/386/bin/ip/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/386/lib/.dummy b/386/lib/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/68000/bin/.dummy b/68000/bin/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/68000/bin/ip/.dummy b/68000/bin/ip/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/68000/lib/.dummy b/68000/lib/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/68020/bin/.dummy b/68020/bin/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/68020/bin/ip/.dummy b/68020/bin/ip/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/68020/lib/.dummy b/68020/lib/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/acme/.dummy b/acme/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/acme/acid/Acid b/acme/acid/Acid
new file mode 100755 (executable)
index 0000000..fa508ca
--- /dev/null
@@ -0,0 +1,6 @@
+#!/bin/rc
+if(~ $#* 0){
+       echo usage: Acid pid >[2=1]
+       exit usage
+}
+win acid -l acme $*
diff --git a/acme/acid/guide b/acme/acid/guide
new file mode 100644 (file)
index 0000000..185b20b
--- /dev/null
@@ -0,0 +1,3 @@
+broke|rc    kill program|rc
+Acid pid
+Acid -l thread -l acidfile pid
diff --git a/acme/bin/386/.dummy b/acme/bin/386/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/acme/bin/Battery b/acme/bin/Battery
new file mode 100755 (executable)
index 0000000..679074c
--- /dev/null
@@ -0,0 +1,30 @@
+#!/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
diff --git a/acme/bin/Isspam b/acme/bin/Isspam
new file mode 100755 (executable)
index 0000000..2c30d77
--- /dev/null
@@ -0,0 +1,13 @@
+#!/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
diff --git a/acme/bin/Mail b/acme/bin/Mail
new file mode 100755 (executable)
index 0000000..e1ae572
--- /dev/null
@@ -0,0 +1,9 @@
+#!/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 $*
diff --git a/acme/bin/Perl b/acme/bin/Perl
new file mode 100755 (executable)
index 0000000..c3544bc
--- /dev/null
@@ -0,0 +1,7 @@
+#!/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]
diff --git a/acme/bin/README b/acme/bin/README
new file mode 100644 (file)
index 0000000..97667dc
--- /dev/null
@@ -0,0 +1,3 @@
+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.
diff --git a/acme/bin/Spam b/acme/bin/Spam
new file mode 100755 (executable)
index 0000000..d423b8e
--- /dev/null
@@ -0,0 +1,13 @@
+#!/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
diff --git a/acme/bin/Unspam b/acme/bin/Unspam
new file mode 100755 (executable)
index 0000000..b68d2c7
--- /dev/null
@@ -0,0 +1,13 @@
+#!/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
diff --git a/acme/bin/adiff b/acme/bin/adiff
new file mode 100755 (executable)
index 0000000..3d3d188
--- /dev/null
@@ -0,0 +1,24 @@
+#!/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
diff --git a/acme/bin/agrep b/acme/bin/agrep
new file mode 100755 (executable)
index 0000000..7d12344
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/rc
+
+exec grep -n $* /dev/null
diff --git a/acme/bin/alpha/.dummy b/acme/bin/alpha/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/acme/bin/ap b/acme/bin/ap
new file mode 100755 (executable)
index 0000000..d84fe0f
--- /dev/null
@@ -0,0 +1,12 @@
+#!/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
diff --git a/acme/bin/arm/.dummy b/acme/bin/arm/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/acme/bin/aspell b/acme/bin/aspell
new file mode 100755 (executable)
index 0000000..7adbd14
--- /dev/null
@@ -0,0 +1,43 @@
+#!/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
diff --git a/acme/bin/dial/.dummy b/acme/bin/dial/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/acme/bin/guide b/acme/bin/guide
new file mode 100644 (file)
index 0000000..eb54a63
--- /dev/null
@@ -0,0 +1,4 @@
+win
+aspell file
+adiff file1 file2
+adict -d oed
diff --git a/acme/bin/ind b/acme/bin/ind
new file mode 100755 (executable)
index 0000000..b871306
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/rc
+
+sed 's/^/      /' $*
diff --git a/acme/bin/mips/.dummy b/acme/bin/mips/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/acme/bin/new b/acme/bin/new
new file mode 100755 (executable)
index 0000000..52990d0
--- /dev/null
@@ -0,0 +1,10 @@
+#!/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
diff --git a/acme/bin/power/.dummy b/acme/bin/power/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/acme/bin/power64/.dummy b/acme/bin/power64/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/acme/bin/quote b/acme/bin/quote
new file mode 100755 (executable)
index 0000000..5c85df7
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/rc
+
+sed 's/^/> /' $*
diff --git a/acme/bin/source/acd/README b/acme/bin/source/acd/README
new file mode 100644 (file)
index 0000000..4fba5b0
--- /dev/null
@@ -0,0 +1,33 @@
+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
diff --git a/acme/bin/source/acd/access b/acme/bin/source/acd/access
new file mode 100644 (file)
index 0000000..9a34399
--- /dev/null
@@ -0,0 +1,243 @@
+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
diff --git a/acme/bin/source/acd/acd.h b/acme/bin/source/acd/acd.h
new file mode 100644 (file)
index 0000000..90c4bdb
--- /dev/null
@@ -0,0 +1,171 @@
+#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*);
+
+
diff --git a/acme/bin/source/acd/acme.c b/acme/bin/source/acd/acme.c
new file mode 100644 (file)
index 0000000..514bdbc
--- /dev/null
@@ -0,0 +1,347 @@
+#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;
+               }
+       }
+}
diff --git a/acme/bin/source/acd/cddb b/acme/bin/source/acd/cddb
new file mode 100644 (file)
index 0000000..be909a8
--- /dev/null
@@ -0,0 +1,206 @@
+<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>&nbsp;&nbsp;<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>&nbsp;</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>
+       &nbsp;
+       </tr></td>
+       </table></tr></td></table></center></td><td>&nbsp;</td>
+
+
+</tr></table></td></tr></table><br><br>
+
+<font face=Arial,Helvetica size=1><center>
+<br>
+<br>
+<br>
+<br>
+</body>
+</html>
diff --git a/acme/bin/source/acd/cddb.c b/acme/bin/source/acd/cddb.c
new file mode 100644 (file)
index 0000000..ba1fa76
--- /dev/null
@@ -0,0 +1,197 @@
+#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);
+}
diff --git a/acme/bin/source/acd/cddbproto b/acme/bin/source/acd/cddbproto
new file mode 100644 (file)
index 0000000..d1a2783
--- /dev/null
@@ -0,0 +1,894 @@
+<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>&nbsp;&nbsp;<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>&nbsp;</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
+-&gt; : client to server\r
+&lt;- : 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
+&lt;- 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
+-&gt; 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
+&lt;- 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
+-&gt; 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
+&lt;- code categ discid dtitle\r
+       or\r
+&lt;- code close matches found\r
+&lt;- categ discid dtitle\r
+&lt;- categ discid dtitle\r
+&lt;- (more matches...)\r
+&lt;- .\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
+-&gt; 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
+&lt;- code categ discid\r
+&lt;- # xmcd 2.0 CD database file\r
+&lt;- # ...\r
+&lt;- (CDDB data...)\r
+&lt;- .\r
+       or\r
+&lt;- 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
+-&gt; 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
+&lt;- code matches found\r
+&lt;- categ discid dtitle\r
+&lt;- categ discid dtitle\r
+&lt;- (more matches...)\r
+&lt;- .\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
+-&gt; 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
+&lt;- 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
+-&gt; # xmcd 2.0 CD database file\r
+-&gt; # ...\r
+-&gt; (CDDB data)\r
+-&gt; .\r
+\r
+Server response:\r
+&lt;- 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
+-&gt; help\r
+       or\r
+-&gt; help cmd\r
+\r
+    cmd:\r
+       CDDB command.  Example: quit\r
+\r
+       or\r
+\r
+-&gt; help cmd subcmd\r
+\r
+    cmd:\r
+       CDDB command.  Example: cddb\r
+    subcmd:\r
+       CDDB command argument.  Example: query\r
+\r
+Server response:\r
+&lt;- code Help information follows\r
+&lt;- (help data ...)\r
+&lt;- .\r
+       or\r
+&lt;- 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
+-&gt; 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
+&lt;- code Log summary follows\r
+&lt;- (log stats)\r
+&lt;- .\r
+       or\r
+&lt;- code Log follows\r
+&lt;- (log stats)\r
+&lt;- .\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
+-&gt; motd\r
+\r
+Server response:\r
+&lt;- code Last modified: date MOTD follows (until terminating marker)\r
+&lt;- (message text)\r
+&lt;- .\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
+-&gt; proto [level]\r
+\r
+    level:\r
+       The (numerical) protocol level to set the server to.\r
+\r
+Server response:\r
+&lt;- code CDDB protocol level: current cur_level, supported supported_level\r
+       or\r
+&lt;- 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
+-&gt; sites\r
+\r
+Server response:\r
+&lt;- code OK, site information follows (until terminating `.')\r
+&lt;- (data)\r
+&lt;- .\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
+-&gt; stat\r
+\r
+Server response:\r
+&lt;- code OK, status information follows (until terminating `.')\r
+&lt;- (data)\r
+&lt;- .\r
+\r
+    code:\r
+       210     Ok, status information follows\r
+\r
+    The possible data is as follows:\r
+       current proto: &lt;current_level&gt;\r
+           An integer representing the server's current operating protocol\r
+           level.\r
+       max proto:     &lt;max_level&gt;\r
+           The maximum supported protocol level.\r
+       gets:          &lt;yes | no&gt;\r
+           Whether or not the client is allowed to get log information,\r
+           according to the string "yes" or "no".\r
+       updates:       &lt;yes | no&gt;\r
+           Whether or not the client is allowed to initiate a database\r
+           update, according to the string "yes" or "no".\r
+       posting:       &lt;yes | no&gt;\r
+           Whether or not the client is allowed to post new entries,\r
+           according to the string "yes" or "no".\r
+       quotes:        &lt;yes | no&gt;\r
+           Whether or not quoted arguments are enabled, according to\r
+           the string "yes" or "no".\r
+       current users: &lt;num_users&gt;\r
+           The number of users currently connected to the server.\r
+       max users:     &lt;num_max_users&gt;\r
+           The number of users that can concurrently connect to the server.\r
+       strip ext:      &lt;yes | no&gt;\r
+           Whether or not extended data is stripped by the server before\r
+           presented to the user.\r
+       Database entries: &lt;num_db_entries&gt;\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
+               &lt;white space&gt;catgory: &lt;num_db_entries&gt;\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
+               &lt;white space&gt;site: &lt;num_db_entries&gt;\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
+-&gt; ver\r
+\r
+Server response:\r
+&lt;- code servername version copyright\r
+       or\r
+&lt;- 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
+-&gt; update\r
+\r
+Server response:\r
+&lt;- code Updating the database.\r
+       or\r
+&lt;- code Permission denied.\r
+       or\r
+&lt;- 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
+-&gt; whom\r
+\r
+Server response:\r
+&lt;- 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
+-&gt; quit\r
+\r
+Server response:\r
+&lt;- 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
+&lt;- 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&amp;hello=joe+my.host.com+clientname+version&amp;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
+"&amp;" 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&amp;hello=joe+my.host.com+xmcd+2.1&amp;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>
+       &nbsp;
+       </tr></td>
+       </table></tr></td></table></center></td><td>&nbsp;</td>
+
+
+</tr></table></td></tr></table><br><br>
+
+<font face=Arial,Helvetica size=1><center>
+<br>
+<br>
+<br>
+<br>
+</body>
+</html>
diff --git a/acme/bin/source/acd/discid b/acme/bin/source/acd/discid
new file mode 100644 (file)
index 0000000..1bc9dbf
--- /dev/null
@@ -0,0 +1,159 @@
+\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
diff --git a/acme/bin/source/acd/mailinglist b/acme/bin/source/acd/mailinglist
new file mode 100644 (file)
index 0000000..002fd8d
--- /dev/null
@@ -0,0 +1,220 @@
+<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>&nbsp;&nbsp;<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>&nbsp;</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>
+       &nbsp;
+       </tr></td>
+       </table></tr></td></table></center></td><td>&nbsp;</td>
+
+
+</tr></table></td></tr></table><br><br>
+
+<font face=Arial,Helvetica size=1><center>
+<br>
+<br>
+<br>
+<br>
+</body>
+</html>
diff --git a/acme/bin/source/acd/main.c b/acme/bin/source/acd/main.c
new file mode 100644 (file)
index 0000000..2ac388f
--- /dev/null
@@ -0,0 +1,135 @@
+#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);
+}
diff --git a/acme/bin/source/acd/mkfile b/acme/bin/source/acd/mkfile
new file mode 100644 (file)
index 0000000..7b549a6
--- /dev/null
@@ -0,0 +1,22 @@
+</$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
diff --git a/acme/bin/source/acd/mmc.c b/acme/bin/source/acd/mmc.c
new file mode 100644 (file)
index 0000000..0a466c4
--- /dev/null
@@ -0,0 +1,303 @@
+#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);
+       }
+}
diff --git a/acme/bin/source/acd/outline b/acme/bin/source/acd/outline
new file mode 100644 (file)
index 0000000..5ab337d
--- /dev/null
@@ -0,0 +1,32 @@
+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.
diff --git a/acme/bin/source/acd/submit b/acme/bin/source/acd/submit
new file mode 100644 (file)
index 0000000..49aaf16
--- /dev/null
@@ -0,0 +1,220 @@
+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
diff --git a/acme/bin/source/acd/toc.c b/acme/bin/source/acd/toc.c
new file mode 100644 (file)
index 0000000..c447949
--- /dev/null
@@ -0,0 +1,59 @@
+#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);
+
+}
diff --git a/acme/bin/source/acd/util.c b/acme/bin/source/acd/util.c
new file mode 100644 (file)
index 0000000..2b300fc
--- /dev/null
@@ -0,0 +1,89 @@
+#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");
+}
diff --git a/acme/bin/source/acd/win.c b/acme/bin/source/acd/win.c
new file mode 100644 (file)
index 0000000..2f37896
--- /dev/null
@@ -0,0 +1,320 @@
+#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;
+}
diff --git a/acme/bin/source/adict/_adict.c b/acme/bin/source/adict/_adict.c
new file mode 100644 (file)
index 0000000..f2c14f5
--- /dev/null
@@ -0,0 +1,584 @@
+#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;
+               }
+       }
+}
diff --git a/acme/bin/source/adict/_win.c b/acme/bin/source/adict/_win.c
new file mode 100644 (file)
index 0000000..18657c5
--- /dev/null
@@ -0,0 +1,315 @@
+#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;
+}
diff --git a/acme/bin/source/adict/adict.c b/acme/bin/source/adict/adict.c
new file mode 100644 (file)
index 0000000..7ce53e5
--- /dev/null
@@ -0,0 +1,591 @@
+#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;
+               }
+       }
+}
diff --git a/acme/bin/source/adict/adict.h b/acme/bin/source/adict/adict.h
new file mode 100644 (file)
index 0000000..fffaa54
--- /dev/null
@@ -0,0 +1,10 @@
+enum
+{
+       Matchwin,
+       Entrywin,
+       Dictwin
+};
+
+#define MAXTAG 20
+#define MAXMATCH 100
+#define BUFSIZE        4096
diff --git a/acme/bin/source/adict/man b/acme/bin/source/adict/man
new file mode 100644 (file)
index 0000000..ad15bf0
--- /dev/null
@@ -0,0 +1,26 @@
+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
diff --git a/acme/bin/source/adict/mkfile b/acme/bin/source/adict/mkfile
new file mode 100644 (file)
index 0000000..14a0798
--- /dev/null
@@ -0,0 +1,11 @@
+</$objtype/mkfile
+
+TARG=adict
+
+HFILES=win.h
+
+OFILES=adict.$O\
+               win.$O\
+
+BIN= /acme/bin/$objtype
+</sys/src/cmd/mkone
diff --git a/acme/bin/source/adict/win.c b/acme/bin/source/adict/win.c
new file mode 100644 (file)
index 0000000..e8b2ee2
--- /dev/null
@@ -0,0 +1,315 @@
+#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;
+}
diff --git a/acme/bin/source/adict/win.h b/acme/bin/source/adict/win.h
new file mode 100644 (file)
index 0000000..8e1698a
--- /dev/null
@@ -0,0 +1,59 @@
+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);
diff --git a/acme/bin/source/mkfile b/acme/bin/source/mkfile
new file mode 100644 (file)
index 0000000..4814633
--- /dev/null
@@ -0,0 +1,27 @@
+</$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
+       }
diff --git a/acme/bin/source/mkwnew.c b/acme/bin/source/mkwnew.c
new file mode 100644 (file)
index 0000000..788e9d3
--- /dev/null
@@ -0,0 +1,45 @@
+#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);
+}
diff --git a/acme/bin/source/spout.c b/acme/bin/source/spout.c
new file mode 100644 (file)
index 0000000..0242978
--- /dev/null
@@ -0,0 +1,123 @@
+#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);
+}
diff --git a/acme/bin/source/win/_fs.c b/acme/bin/source/win/_fs.c
new file mode 100644 (file)
index 0000000..6791a21
--- /dev/null
@@ -0,0 +1,146 @@
+#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);
+}
diff --git a/acme/bin/source/win/_main.c b/acme/bin/source/win/_main.c
new file mode 100644 (file)
index 0000000..4ac9c19
--- /dev/null
@@ -0,0 +1,651 @@
+#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, &notepg);
+
+       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);
+}
diff --git a/acme/bin/source/win/dat.h b/acme/bin/source/win/dat.h
new file mode 100644 (file)
index 0000000..38d31dc
--- /dev/null
@@ -0,0 +1,95 @@
+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;
+
diff --git a/acme/bin/source/win/fs.c b/acme/bin/source/win/fs.c
new file mode 100644 (file)
index 0000000..6c41eb0
--- /dev/null
@@ -0,0 +1,147 @@
+#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);
+}
diff --git a/acme/bin/source/win/main.c b/acme/bin/source/win/main.c
new file mode 100644 (file)
index 0000000..916f2b0
--- /dev/null
@@ -0,0 +1,646 @@
+#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, &notepg);
+
+       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);
+}
diff --git a/acme/bin/source/win/mkfile b/acme/bin/source/win/mkfile
new file mode 100644 (file)
index 0000000..608f95b
--- /dev/null
@@ -0,0 +1,25 @@
+</$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
diff --git a/acme/bin/source/win/pipe.c b/acme/bin/source/win/pipe.c
new file mode 100644 (file)
index 0000000..280f726
--- /dev/null
@@ -0,0 +1,175 @@
+#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);
+}
diff --git a/acme/bin/source/win/util.c b/acme/bin/source/win/util.c
new file mode 100644 (file)
index 0000000..ec8bd0c
--- /dev/null
@@ -0,0 +1,90 @@
+#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");
+}
diff --git a/acme/bin/source/win/win.c b/acme/bin/source/win/win.c
new file mode 100644 (file)
index 0000000..2e7f883
--- /dev/null
@@ -0,0 +1,264 @@
+#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;
+}
diff --git a/acme/bin/sparc/.dummy b/acme/bin/sparc/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/acme/bin/sparc64/.dummy b/acme/bin/sparc64/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/acme/bin/unind b/acme/bin/unind
new file mode 100755 (executable)
index 0000000..6b1e59f
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/rc
+
+sed 's/^       //' $*
diff --git a/acme/bin/wnew b/acme/bin/wnew
new file mode 100755 (executable)
index 0000000..f136217
--- /dev/null
@@ -0,0 +1,5 @@
+#!/bin/rc -e
+
+id=`{mkwnew $*}
+cat >/mnt/acme/$id/body
+echo clean >/mnt/acme/$id/ctl
diff --git a/acme/mail/guide b/acme/mail/guide
new file mode 100644 (file)
index 0000000..8977ac7
--- /dev/null
@@ -0,0 +1,4 @@
+Mail stored
+plumb /mail/box/$user/names
+mail -'x' someaddress
+mkbox /mail/box/$user/new_box
diff --git a/acme/mail/mkbox b/acme/mail/mkbox
new file mode 100755 (executable)
index 0000000..3e98afc
--- /dev/null
@@ -0,0 +1,11 @@
+#!/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
+}
diff --git a/acme/mail/readme b/acme/mail/readme
new file mode 100644 (file)
index 0000000..b795a04
--- /dev/null
@@ -0,0 +1,57 @@
+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.
diff --git a/acme/mail/src/dat.h b/acme/mail/src/dat.h
new file mode 100644 (file)
index 0000000..0b0b60a
--- /dev/null
@@ -0,0 +1,164 @@
+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;
diff --git a/acme/mail/src/html.c b/acme/mail/src/html.c
new file mode 100644 (file)
index 0000000..6473085
--- /dev/null
@@ -0,0 +1,75 @@
+#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;
+}
diff --git a/acme/mail/src/mail.c b/acme/mail/src/mail.c
new file mode 100644 (file)
index 0000000..1e1ff0b
--- /dev/null
@@ -0,0 +1,550 @@
+#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;
+                       }
+               }
+       }
+}
+
diff --git a/acme/mail/src/mesg.c b/acme/mail/src/mesg.c
new file mode 100644 (file)
index 0000000..620e76e
--- /dev/null
@@ -0,0 +1,1322 @@
+#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);
+}
diff --git a/acme/mail/src/mkfile b/acme/mail/src/mkfile
new file mode 100644 (file)
index 0000000..aa6545d
--- /dev/null
@@ -0,0 +1,30 @@
+</$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
+
diff --git a/acme/mail/src/reply.c b/acme/mail/src/reply.c
new file mode 100644 (file)
index 0000000..65841c5
--- /dev/null
@@ -0,0 +1,567 @@
+#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;
+}
diff --git a/acme/mail/src/util.c b/acme/mail/src/util.c
new file mode 100644 (file)
index 0000000..c32de5f
--- /dev/null
@@ -0,0 +1,105 @@
+#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");
+}
diff --git a/acme/mail/src/win.c b/acme/mail/src/win.c
new file mode 100644 (file)
index 0000000..53d4ed1
--- /dev/null
@@ -0,0 +1,341 @@
+#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;
+}
diff --git a/acme/mkfile b/acme/mkfile
new file mode 100755 (executable)
index 0000000..fc6fc9a
--- /dev/null
@@ -0,0 +1,10 @@
+</$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}
diff --git a/acme/news/guide b/acme/news/guide
new file mode 100644 (file)
index 0000000..845b0ae
--- /dev/null
@@ -0,0 +1,2 @@
+Local nntpfs
+News comp.os.plan9
diff --git a/acme/news/src/mkfile b/acme/news/src/mkfile
new file mode 100644 (file)
index 0000000..34979b2
--- /dev/null
@@ -0,0 +1,18 @@
+</$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
diff --git a/acme/news/src/news.c b/acme/news/src/news.c
new file mode 100644 (file)
index 0000000..90cb3c7
--- /dev/null
@@ -0,0 +1,1007 @@
+/*
+ * 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);
+}
diff --git a/acme/news/src/util.c b/acme/news/src/util.c
new file mode 100644 (file)
index 0000000..d3e6fda
--- /dev/null
@@ -0,0 +1,106 @@
+#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);
+}
diff --git a/acme/news/src/win.c b/acme/news/src/win.c
new file mode 100644 (file)
index 0000000..eebebd4
--- /dev/null
@@ -0,0 +1,324 @@
+#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;
+}
diff --git a/acme/news/src/win.h b/acme/news/src/win.h
new file mode 100644 (file)
index 0000000..414e6e3
--- /dev/null
@@ -0,0 +1,75 @@
+/* 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*);
+
diff --git a/acme/wiki/guide b/acme/wiki/guide
new file mode 100644 (file)
index 0000000..a863325
--- /dev/null
@@ -0,0 +1,3 @@
+Local 9fs wiki
+# Local wikifs /sys/lib/wiki
+Wiki /mnt/wiki
diff --git a/acme/wiki/src/awiki.h b/acme/wiki/src/awiki.h
new file mode 100644 (file)
index 0000000..19fd617
--- /dev/null
@@ -0,0 +1,114 @@
+#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*);
+
diff --git a/acme/wiki/src/main.c b/acme/wiki/src/main.c
new file mode 100644 (file)
index 0000000..f76caff
--- /dev/null
@@ -0,0 +1,60 @@
+#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);
+}
diff --git a/acme/wiki/src/mkfile b/acme/wiki/src/mkfile
new file mode 100644 (file)
index 0000000..89431e7
--- /dev/null
@@ -0,0 +1,14 @@
+</$objtype/mkfile
+
+TARG=Wiki
+
+OFILES=\
+       main.$O\
+       util.$O\
+       wiki.$O\
+       win.$O\
+
+HFILES=awiki.h
+BIN=../../bin/$objtype
+
+</sys/src/cmd/mkone
diff --git a/acme/wiki/src/util.c b/acme/wiki/src/util.c
new file mode 100644 (file)
index 0000000..832d973
--- /dev/null
@@ -0,0 +1,89 @@
+#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);
+}
diff --git a/acme/wiki/src/wiki.c b/acme/wiki/src/wiki.c
new file mode 100644 (file)
index 0000000..ca53c3a
--- /dev/null
@@ -0,0 +1,602 @@
+#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;
+}
+
diff --git a/acme/wiki/src/win.c b/acme/wiki/src/win.c
new file mode 100644 (file)
index 0000000..3eec1e9
--- /dev/null
@@ -0,0 +1,341 @@
+#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;
+}
diff --git a/acme/wiki/wiki.diff b/acme/wiki/wiki.diff
new file mode 100755 (executable)
index 0000000..6214571
--- /dev/null
@@ -0,0 +1,27 @@
+#!/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
diff --git a/alpha/bin/.dummy b/alpha/bin/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/alpha/bin/ip/.dummy b/alpha/bin/ip/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/amd64/bin/.dummy b/amd64/bin/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/amd64/bin/ip/.dummy b/amd64/bin/ip/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/amd64/lib/.dummy b/amd64/lib/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/arm/bin/.dummy b/arm/bin/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/arm/bin/ip/.dummy b/arm/bin/ip/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/arm/lib/.dummy b/arm/lib/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/mips/bin/.dummy b/mips/bin/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/mips/lib/.dummy b/mips/lib/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/power/bin/.dummy b/power/bin/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/power/bin/ip/.dummy b/power/bin/ip/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/power/lib/.dummy b/power/lib/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/power64/bin/.dummy b/power64/bin/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/power64/bin/ip/.dummy b/power64/bin/ip/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/power64/lib/.dummy b/power64/lib/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/sparc/bin/.dummy b/sparc/bin/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/sparc/bin/ip/.dummy b/sparc/bin/ip/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/sparc/lib/.dummy b/sparc/lib/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/sparc64/bin/.dummy b/sparc64/bin/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/sparc64/bin/ip/.dummy b/sparc64/bin/ip/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/sparc64/lib/.dummy b/sparc64/lib/.dummy
deleted file mode 100644 (file)
index e69de29..0000000