summaryrefslogtreecommitdiff
path: root/acme/bin
diff options
context:
space:
mode:
Diffstat (limited to 'acme/bin')
-rw-r--r--acme/bin/386/.dummy0
-rwxr-xr-xacme/bin/Battery30
-rwxr-xr-xacme/bin/Isspam13
-rwxr-xr-xacme/bin/Mail9
-rwxr-xr-xacme/bin/Perl7
-rw-r--r--acme/bin/README3
-rwxr-xr-xacme/bin/Spam13
-rwxr-xr-xacme/bin/Unspam13
-rwxr-xr-xacme/bin/adiff24
-rwxr-xr-xacme/bin/agrep3
-rw-r--r--acme/bin/alpha/.dummy0
-rwxr-xr-xacme/bin/ap12
-rw-r--r--acme/bin/arm/.dummy0
-rwxr-xr-xacme/bin/aspell43
-rw-r--r--acme/bin/dial/.dummy0
-rw-r--r--acme/bin/guide4
-rwxr-xr-xacme/bin/ind3
-rw-r--r--acme/bin/mips/.dummy0
-rwxr-xr-xacme/bin/new10
-rw-r--r--acme/bin/power/.dummy0
-rw-r--r--acme/bin/power64/.dummy0
-rwxr-xr-xacme/bin/quote3
-rw-r--r--acme/bin/source/acd/README33
-rw-r--r--acme/bin/source/acd/access243
-rw-r--r--acme/bin/source/acd/acd.h171
-rw-r--r--acme/bin/source/acd/acme.c347
-rw-r--r--acme/bin/source/acd/cddb206
-rw-r--r--acme/bin/source/acd/cddb.c197
-rw-r--r--acme/bin/source/acd/cddbproto894
-rw-r--r--acme/bin/source/acd/discid159
-rw-r--r--acme/bin/source/acd/mailinglist220
-rw-r--r--acme/bin/source/acd/main.c135
-rw-r--r--acme/bin/source/acd/mkfile22
-rw-r--r--acme/bin/source/acd/mmc.c303
-rw-r--r--acme/bin/source/acd/outline32
-rw-r--r--acme/bin/source/acd/submit220
-rw-r--r--acme/bin/source/acd/toc.c59
-rw-r--r--acme/bin/source/acd/util.c89
-rw-r--r--acme/bin/source/acd/win.c320
-rw-r--r--acme/bin/source/adict/_adict.c584
-rw-r--r--acme/bin/source/adict/_win.c315
-rw-r--r--acme/bin/source/adict/adict.c591
-rw-r--r--acme/bin/source/adict/adict.h10
-rw-r--r--acme/bin/source/adict/man26
-rw-r--r--acme/bin/source/adict/mkfile11
-rw-r--r--acme/bin/source/adict/win.c315
-rw-r--r--acme/bin/source/adict/win.h59
-rw-r--r--acme/bin/source/mkfile27
-rw-r--r--acme/bin/source/mkwnew.c45
-rw-r--r--acme/bin/source/spout.c123
-rw-r--r--acme/bin/source/win/_fs.c146
-rw-r--r--acme/bin/source/win/_main.c651
-rw-r--r--acme/bin/source/win/dat.h95
-rw-r--r--acme/bin/source/win/fs.c147
-rw-r--r--acme/bin/source/win/main.c646
-rw-r--r--acme/bin/source/win/mkfile25
-rw-r--r--acme/bin/source/win/pipe.c175
-rw-r--r--acme/bin/source/win/util.c90
-rw-r--r--acme/bin/source/win/win.c264
-rw-r--r--acme/bin/sparc/.dummy0
-rw-r--r--acme/bin/sparc64/.dummy0
-rwxr-xr-xacme/bin/unind3
-rwxr-xr-xacme/bin/wnew5
63 files changed, 8193 insertions, 0 deletions
diff --git a/acme/bin/386/.dummy b/acme/bin/386/.dummy
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/acme/bin/386/.dummy
diff --git a/acme/bin/Battery b/acme/bin/Battery
new file mode 100755
index 000000000..679074c63
--- /dev/null
+++ b/acme/bin/Battery
@@ -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
index 000000000..2c30d7703
--- /dev/null
+++ b/acme/bin/Isspam
@@ -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
index 000000000..e1ae5729e
--- /dev/null
+++ b/acme/bin/Mail
@@ -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
index 000000000..c3544bcb6
--- /dev/null
+++ b/acme/bin/Perl
@@ -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
index 000000000..97667dc13
--- /dev/null
+++ b/acme/bin/README
@@ -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
index 000000000..d423b8ec2
--- /dev/null
+++ b/acme/bin/Spam
@@ -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
index 000000000..b68d2c7c0
--- /dev/null
+++ b/acme/bin/Unspam
@@ -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
index 000000000..3d3d188f5
--- /dev/null
+++ b/acme/bin/adiff
@@ -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
index 000000000..7d1234477
--- /dev/null
+++ b/acme/bin/agrep
@@ -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
index 000000000..e69de29bb
--- /dev/null
+++ b/acme/bin/alpha/.dummy
diff --git a/acme/bin/ap b/acme/bin/ap
new file mode 100755
index 000000000..d84fe0f33
--- /dev/null
+++ b/acme/bin/ap
@@ -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
index 000000000..e69de29bb
--- /dev/null
+++ b/acme/bin/arm/.dummy
diff --git a/acme/bin/aspell b/acme/bin/aspell
new file mode 100755
index 000000000..7adbd148f
--- /dev/null
+++ b/acme/bin/aspell
@@ -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
index 000000000..e69de29bb
--- /dev/null
+++ b/acme/bin/dial/.dummy
diff --git a/acme/bin/guide b/acme/bin/guide
new file mode 100644
index 000000000..eb54a633c
--- /dev/null
+++ b/acme/bin/guide
@@ -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
index 000000000..b87130651
--- /dev/null
+++ b/acme/bin/ind
@@ -0,0 +1,3 @@
+#!/bin/rc
+
+sed 's/^/ /' $*
diff --git a/acme/bin/mips/.dummy b/acme/bin/mips/.dummy
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/acme/bin/mips/.dummy
diff --git a/acme/bin/new b/acme/bin/new
new file mode 100755
index 000000000..52990d096
--- /dev/null
+++ b/acme/bin/new
@@ -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
index 000000000..e69de29bb
--- /dev/null
+++ b/acme/bin/power/.dummy
diff --git a/acme/bin/power64/.dummy b/acme/bin/power64/.dummy
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/acme/bin/power64/.dummy
diff --git a/acme/bin/quote b/acme/bin/quote
new file mode 100755
index 000000000..5c85df7c2
--- /dev/null
+++ b/acme/bin/quote
@@ -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
index 000000000..4fba5b0c5
--- /dev/null
+++ b/acme/bin/source/acd/README
@@ -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
index 000000000..9a34399e0
--- /dev/null
+++ b/acme/bin/source/acd/access
@@ -0,0 +1,243 @@
+TWO FORMS OF ACCESS TO THE FREEDB
+---------------------------------
+
+In the following document we will refer to CDDB instead of freedb, since
+from a technical point of view, freedb is a CDDB-Server as it uses the
+CDDB-protocol.
+
+If you are interested in incorporating the use of freedb in your
+software, there are two forms of access that you may consider.
+
+1. <a href="#local">Local access</a>
+
+ In this mode your software simply attempts to open local files on
+ the computer to access the CDDB.
+
+2. <a href="#remote">Remote access</a>
+
+ In this mode the software must connect to a freedb server on the
+ network to access the CDDB. There is a CDDB server protocol that
+ the software (also known as the "client") must use to converse with
+ the server.
+
+You may choose to support either one, or both of these modes.
+
+
+CDDB DISCID
+-----------
+
+Both forms of CDDB access requires that the software computes a "disc
+ID" which is an identifier that is used to access the CDDB. The disc
+ID is a 8-digit hexadecimal (base-16) number, computed using data from
+a CD's Table-of-Contents (TOC) in MSF (Minute Second Frame) form. The
+algorithm is listed below in the <a href="http://freedb.freedb.org/sections.php?op=viewarticle&artid=6">DISCID Howto</a>.
+
+It is crucial that your software compute the disc ID correctly. If it
+does not generate the disc ID, it will not be compatible with the
+CDDB. Moreover, if your software submits CDDB entries with bad disc
+IDs to the freedb archives, it could compromise the integrity of the
+freedb.
+
+If you have access to a UNIX platform that xmcd supports, we suggest
+installing xmcd, and then test the disc ID code in your software by
+comparing the disc ID generated by xmcd with that of your software,
+for as large a number of CDs as possible.
+
+
+<a name="local"></a>LOCAL CDDB ACCESS
+-----------------
+
+There are two forms of the CDDB archive available, the standard form
+and the alternate form. Both forms are available for download from
+various servers. You can always find an actual list of mirrors on the
+freedb-homepage at <a href="http://freedb.freedb.org">http://freedb.freedb.org</a>.
+The standard form of the CDDB archive is released to the public as
+a UNIX tar(1)-format archive, compressed with gzip. The alternate
+form archive is in the .zip format that is popular on the Windows
+platform.
+
+Standard Form:
+--------------
+
+Each CD entry is a separate file in the xmcd CDDB. These files are
+organized in several directories, each directory is a category of
+music. Currently the "official" categories are listed as follows:
+
+ blues
+ classical
+ country
+ data
+ folk
+ jazz
+ misc
+ newage
+ reggae
+ rock
+ soundtrack
+
+The individual CDDB files have a file name that is the 8-digit disc
+ID. For example, under the blues directory there may be the following
+files:
+
+ 0511c012
+ 060e7314
+ 0c01e902
+ 0f0c3112
+ ...
+ fa0f6f10
+ fb0f8814
+ fd0e6013
+
+To access the CDDB entry associated with a CD, your software simply
+opens the appropriate file and reads the information.
+
+The content of each of these files is in a format described in the
+<a href="http://freedb.freedb.org/software/old/DBFORMAT">database-format specification</a>.
+
+Different pressings of a particular CD title may contain differences
+in timings that can cause the computed disc ID to be different.
+The CDDB allows this by having multiple file names be links to
+the same file. The links are implemented as actual filesystem links
+(see the ln(1) command) on UNIX systems. For example, the following
+files in the rock directory are all links to the same file, and
+refer to the CD "Pink Floyd / The Division Bell".:
+
+ 850f740b
+ 850f950b
+ 850f970b
+ 860f960b
+ 890f970b
+
+Xmcd and the CD database server use this form of the CDDB archive. The
+benefit of the standard form of the CDDB archive is very fast access,
+and ease of add/delete/edit operations on entries.
+
+Alternate Form:
+---------------
+
+Due to limitations in the FAT file system used on Windows 9x and
+Windows ME, it is unfeasible to use the standard format CDDB archive
+due to the large number of files. This is because such a filesystem
+operates on fixed-size clusters and even a small file (and most CDDB
+files are 1KB or less) would consume the space of a full cluster
+(Depending upon disk size, a cluster can range from 4KB to 32KB in
+size). Thus, a tremendous amount of disk space would be wasted on
+these systems if the CDDB archive is used in its standard form.
+
+An alternate form of the CDDB archives was created for use by software
+that must operate on a system with the FAT limitations.
+
+The alterate form still use the separate category directories as the
+standard form, but concatenates many files into a smaller number of
+files under each category. The first two digits of the CDDB file names
+is used as a key for concatenation, each file is allowed to grow to
+approximately 64KB in size before a new file is started. The file name
+indicates what range of the digits are included in that file. For
+example, under the blues category we may have the following files:
+
+ 01to36
+ 37to55
+ 56to71
+ ...
+ b2tod7
+ d8toff
+
+The 01to36 file contains all CDDB entries with disc ID 01xxxxxx,
+02xxxxxx, 03xxxxxx and so on, up to 36xxxxxx.
+
+Each entry in the concatenated file begins with the keyword
+
+#FILENAME=xxxxxxxx
+
+where discid is the 8-digit hexadecimal disc ID of that entry. Your
+software must search through the appropriate file to locate the desired
+entry. The CDDB entry is in the format described in Appendix B below.
+
+The alternate form avoids the problem of inefficient disk space
+utilization on FAT-based filesystems, but is slower to access than the
+standard form, and it is much more cumbersome to perform add/delete/edit
+operations on a CDDB entry.
+
+
+<a name="remote"></a>REMOTE CDDB ACCESS
+------------------
+
+Your software must be able to communicate with a remote CD server
+system via TCP/IP or HTTP.
+There are a number of public freedb servers operating
+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
+freedb web page at:
+
+ http://freedb.freedb.org.
+
+It may also be obtained programmatically via the CDDB protocol "sites"
+command.
+
+TCP/IP access:
+
+All current freedb servers answer at TCP port 888. There may be future
+sites that deviate from this convention, however.
+
+HTTP access:
+
+The freedb-servers can be accessed via the cddb.cgi. This is resides at the
+following path: /~cddb/cddb.cgi
+Thus, the URL for accessing the server at freedb.freedb.org is:
+http://freedb.freedb.org/~cddb/cddb.cgi
+
+You should make the freedb server host (or hosts) and port numbers
+user-configurable in your software. Do not hard-wire the list of
+CD database servers into your code. The list of active servers changes
+over time.
+
+The CDDB server protocol is described in the <a href="http://freedb.freedb.org/sections.php?op=viewarticle&artid=28">CDDB-protocol documentation</a>.
+
+The CDDB entry returned from the server via a "cddb read" command is in
+the format described <a href="http://freedb.freedb.org/software/old/DBFORMAT">database-format specification</a>.
+
+You may experiment with the freedb server by connecting to port 888 of
+the server host via the "telnet" program, and then typing the cddb
+protocol commands by hand. For example:
+
+ telnet freedb.freedb.org 888
+
+connects you to the freedb server at freedb.freedb.org.
+
+Some additional notes for accessing freedb over the Internet:
+
+Your application should always specify the highest documented protocol
+level. The highest level currently supported is "3". Lower protocol
+levels will work, but are only provided for compatibility with older
+CDDB applications. If you do not use the highest available protocol
+level, certain important features will not be available to your
+application.
+
+Make sure to use the proper arguments with the "hello" command. The user
+and hostname arguments should be that of the user's email address, not
+some fixed hard-coded value. The application name and version should be
+that of your application, not that of another existing application.
+
+We consider the use of the "cddb query" command mandatory for all CDDB
+clients. It is not valid to issue a "cddb read" command without issuing
+a prior "cddb query" and receiving a good response, as it may yield incorrect
+results. In addition, it is clients should support close matches
+(aka "fuzzy" matches, or response code 211).
+
+The proper way to handle multiple fuzzy matches is to present the
+entire list of matches to the user and to let the user choose between them.
+Matches are listed in the order of best fit for the user's disc, so they
+should be presented to the user in the order they are listed by the server.
+
+The suggested algorithm for obtaining the list of server sites is
+as follows. The application should offer to get the list from
+freedb.freedb.org with the "sites" command the first time the user runs
+the program. Additionally the application should provide the user with
+some method of downloading the list on-demand.
+
+We do strongly suggest that you provide your users with the capability of
+choosing freedb server sites as described above. However, for some
+applications this may not be feasible. If you do not wish to offer this
+functionality, you may safely hard-code "freedb.freedb.org" in your
+application as the sole freedb site to access. This will deprive your users
+of the option to choose a site near their locale for optimal response, but
+that is your choice.
diff --git a/acme/bin/source/acd/acd.h b/acme/bin/source/acd/acd.h
new file mode 100644
index 000000000..90c4bdb81
--- /dev/null
+++ b/acme/bin/source/acd/acd.h
@@ -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
index 000000000..514bdbc6e
--- /dev/null
+++ b/acme/bin/source/acd/acme.c
@@ -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
index 000000000..be909a8f0
--- /dev/null
+++ b/acme/bin/source/acd/cddb
@@ -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>
+<li><a href=topics.php>News-Topics</a>
+<li><a href=sections.php?op=listarticles&secid=1>About</a>
+<li><a href=sections.php?op=listarticles&secid=2>Developers</a>
+<li><a href=sections.php?op=listarticles&secid=3>Applications</a>
+<li><a href=sections.php?op=listarticles&secid=7>Download</a>
+<li><a href=forum/index.php>Forum</a>
+<li><a href=http://freedb.music.sk/search/>Web-based Search</a>
+<li><a href=links.php>Web Links</a>
+<li><a href=user.php>Your Account</a>
+<li><a href=submit.php>Submit News</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>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>
+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>
+<a href="mailto:info@freedb.org">info@freedb.org</a><hr>
+Databaseupdates:<br>
+<a href="mailto:updates@freedb.org">updates@freedb.org</a><br>
+(<b>NOT</b> for submission!)<hr>
+Please keep in mind that we are NOT the Nero-Support and please do not send CD-submits to the adresses above.<br>
+Submits have to go to:<br>
+<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>
+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
index 000000000..ba1fa7604
--- /dev/null
+++ b/acme/bin/source/acd/cddb.c
@@ -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
index 000000000..d1a278318
--- /dev/null
+++ b/acme/bin/source/acd/cddbproto
@@ -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>
+<li><a href=topics.php>News-Topics</a>
+<li><a href=sections.php?op=listarticles&secid=1>About</a>
+<li><a href=sections.php?op=listarticles&secid=2>Developers</a>
+<li><a href=sections.php?op=listarticles&secid=3>Applications</a>
+<li><a href=sections.php?op=listarticles&secid=7>Download</a>
+<li><a href=forum/index.php>Forum</a>
+<li><a href=http://freedb.music.sk/search/>Web-based Search</a>
+<li><a href=links.php>Web Links</a>
+<li><a href=user.php>Your Account</a>
+<li><a href=submit.php>Submit News</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>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>
+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>
+<a href="mailto:info@freedb.org">info@freedb.org</a><hr>
+Databaseupdates:<br>
+<a href="mailto:updates@freedb.org">updates@freedb.org</a><br>
+(<b>NOT</b> for submission!)<hr>
+Please keep in mind that we are NOT the Nero-Support and please do not send CD-submits to the adresses above.<br>
+Submits have to go to:<br>
+<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>
+ CDDB Protocol
+
+ By Steve Scherf and Ti Kan
+ --------------------------
+
+Revision: $Id: CDDBPROTO,v 1.6 1997/05/14 07:53:52 steve Exp steve $
+
+
+Notation:
+-&gt; : client to server
+&lt;- : server to client
+
+terminating marker: `.' character in the beginning of a line
+
+
+Server response code (three digit code):
+
+First digit:
+1xx Informative message
+2xx Command OK
+3xx Command OK so far, continue
+4xx Command OK, but cannot be performed for some specified reasons
+5xx Command unimplemented, incorrect, or program error
+
+Second digit:
+x0x Ready for further commands
+x1x More server-to-client output follows (until terminating marker)
+x2x More client-to-server input follows (until terminating marker)
+x3x Connection will close
+
+Third digit:
+xx[0-9] Command-specific code
+
+
+CDDB Protocol Level 1:
+----------------------
+
+Server sign-on banner:
+----------------------
+&lt;- code hostname CDDBP server version ready at date
+
+ code:
+ 200 OK, read/write allowed
+ 201 OK, read only
+ 432 No connections allowed: permission denied
+ 433 No connections allowed: X users allowed, Y currently active
+ 434 No connections allowed: system load too high
+ hostname:
+ Server host name. Example: xyz.fubar.com
+ version:
+ Version number of server software. Example: v1.0PL0
+ date:
+ Current date and time. Example: Wed Mar 13 00:41:34 1996
+
+
+Initial client-server handshake:
+--------------------------------
+Note: This handshake must occur before other cddb commands
+ are accepted by the server.
+
+Client command:
+-&gt; cddb hello username hostname clientname version
+
+ username:
+ Login name of user. Example: johndoe
+ hostname:
+ Host name of client. Example: abc.fubar.com
+ clientname:
+ The name of the connecting client. Example: xmcd, cda, EasyCD,
+ et cetera. Do not use the name of another client which already
+ exists.
+ version:
+ Version number of client software. Example: v1.0PL0
+
+Server response:
+&lt;- code hello and welcome username@hostname running clientname version
+
+ code:
+ 200 Handshake successful
+ 431 Handshake not successful, closing connection
+ 402 Already shook hands
+
+
+CDDB query:
+-----------
+Client command:
+-&gt; cddb query discid ntrks off1 off2 ... nsecs
+
+ discid:
+ CD disc ID number. Example: f50a3b13
+ ntrks:
+ Total number of tracks on CD.
+ off1, off2, ...:
+ Frame offset of the starting location of each track.
+ nsecs:
+ Total playing length of CD in seconds.
+
+Server response:
+&lt;- code categ discid dtitle
+ or
+&lt;- code close matches found
+&lt;- categ discid dtitle
+&lt;- categ discid dtitle
+&lt;- (more matches...)
+&lt;- .
+
+ code:
+ 200 Found exact match
+ 211 Found inexact matches, list follows (until terminating marker)
+ 202 No match found
+ 403 Database entry is corrupt
+ 409 No handshake
+ categ:
+ CD category. Example: rock
+ discid:
+ CD disc ID number of the found entry. Example: f50a3b13
+ dtitle:
+ The Disc Artist and Disc Title (The DTITLE line). For example:
+ Pink Floyd / The Dark Side of the Moon
+
+
+CDDB read:
+----------
+Client command:
+-&gt; cddb read categ discid
+
+ categ:
+ CD category. Example: rock
+ discid:
+ CD disc ID number. Example: f50a3b13
+
+Server response:
+&lt;- code categ discid
+&lt;- # xmcd 2.0 CD database file
+&lt;- # ...
+&lt;- (CDDB data...)
+&lt;- .
+ or
+&lt;- code categ discid No such CD entry in database
+
+ code:
+ 210 OK, CDDB database entry follows (until terminating marker)
+ 401 Specified CDDB entry not found.
+ 402 Server error.
+ 403 Database entry is corrupt.
+ 409 No handshake.
+ categ:
+ CD category. Example: rock
+ discid:
+ CD disc ID number. Example: f50a3b13
+
+
+CDDB search: (command not yet implemented in freedb-serversoftware!)
+------------
+Client command:
+-&gt; cddb srch key search_type ... search_type
+
+ key:
+ Pseudo-regular expression to match. Expressions should meet the
+ following description:
+
+ - No white space.
+ - Printable characters only.
+ - Case is ignored.
+ search_type:
+ CDDB fields to search through. Example: title
+ Supported types: artist, title, extd, ext, trk
+ categ:
+ CD category. Example: rock
+
+Server response:
+&lt;- code matches found
+&lt;- categ discid dtitle
+&lt;- categ discid dtitle
+&lt;- (more matches...)
+&lt;- .
+
+ code:
+ 210 OK, matches found, list follows (until terminating marker)
+ 401 No match found.
+ 409 No handshake.
+ categ:
+ CD category. Example: rock
+ dtitle:
+ The Disc Artist and Disc Title (The DTITLE line). For example:
+ Pink Floyd / The Dark Side of the Moon
+
+
+CDDB write:
+-----------
+Client command:
+-&gt; cddb write categ discid
+
+ categ:
+ CD category. Example: rock
+ discid:
+ CD disc ID number. Example: f50a3b13
+
+Server response:
+&lt;- code categ discid
+
+ code:
+ 320 OK, input CDDB data (until terminating marker)
+ 401 Permission denied.
+ 402 Server file system full/file access failed.
+ 409 No handshake.
+ 501 Entry rejected: reason for rejection.
+ categ:
+ CD category. Example: rock
+ discid:
+ CD disc ID number. Example: f50a3b13
+
+Client data:
+-&gt; # xmcd 2.0 CD database file
+-&gt; # ...
+-&gt; (CDDB data)
+-&gt; .
+
+Server response:
+&lt;- code message
+
+ code:
+ 200 CDDB entry accepted
+ 401 CDDB entry rejected: reason why
+ message:
+ Message string to indicate write status:
+ CDDB entry accepted, or CDDB entry rejected.
+
+
+Help information:
+-----------------
+Client command:
+-&gt; help
+ or
+-&gt; help cmd
+
+ cmd:
+ CDDB command. Example: quit
+
+ or
+
+-&gt; help cmd subcmd
+
+ cmd:
+ CDDB command. Example: cddb
+ subcmd:
+ CDDB command argument. Example: query
+
+Server response:
+&lt;- code Help information follows
+&lt;- (help data ...)
+&lt;- .
+ or
+&lt;- code no help information available
+
+ code:
+ 210 OK, help information follows (until terminating marker)
+ 401 No help information available
+
+
+Log statistics:
+---------------
+Client command:
+-&gt; log [[-l lines] [start date [end date]] | [day [days]] | [get"]]
+
+ lines:
+ The maximum number of lines to print for each data list in the
+ log statistics.
+ start date:
+ The date after which statistics should be calculated. Date is
+ of the format: hh[mm[ss[MM[DD[[CC]YY]]]]]
+
+ E.g.: 201200053196 for 8:12 PM on May 31, 1996.
+ 20120005312096 for 8:12 PM on May 31, 2096.
+ 080530 for today at at 8:15 and 30 seconds.
+
+ If the century ("CC") is omitted, a reasonable guess is made. If
+ this argument is omitted, all messages are considered.
+ end date:
+ The date after which statistics should not be calculated. If
+ omitted, the end date is assumed to be the current date.
+ day:
+ The string "day". This solitary argument will cause a log search
+ of messages generated within the last day.
+ days:
+ A positive numerical argument which modifies the number of days'
+ messages to searh. If this argument is left out, the default is 1.
+ get:
+ The string "get". This solitary argument will cause the server
+ to send the contents of the log file.
+
+Server response:
+&lt;- code Log summary follows
+&lt;- (log stats)
+&lt;- .
+ or
+&lt;- code Log follows
+&lt;- (log stats)
+&lt;- .
+
+ code:
+ 210 OK, log summary follows (until terminating marker)
+ 211 OK, log follows (until terminating marker)
+ 401 Permission denied
+ 402 No log information available
+ 501 Invalid start/end date
+
+
+Message of the day:
+------------------
+Client command:
+-&gt; motd
+
+Server response:
+&lt;- code Last modified: date MOTD follows (until terminating marker)
+&lt;- (message text)
+&lt;- .
+
+ code:
+ 210 Last modified: 05/31/96 06:31:14 MOTD follows (until terminating marker)
+ 401 No message of the day available
+ date:
+ The date the text of the message of the day was modified. The date
+ appears in the following format:
+
+ 05/31/96 06:31:14
+
+ This value may be used by client software as a message timestamp
+ for purposes of determining if it has already been displayed. This
+ format was chosen because it is more easily parsed than the standard
+ ctime() format.
+
+
+Server protocol level:
+----------------------
+Client command:
+-&gt; proto [level]
+
+ level:
+ The (numerical) protocol level to set the server to.
+
+Server response:
+&lt;- code CDDB protocol level: current cur_level, supported supported_level
+ or
+&lt;- code OK, protocol version now: cur_level
+
+ code:
+ 200 CDDB protocol level: current cur_level, supported supp_level
+ 201 OK, protocol version now: cur_level
+ 501 Illegal protocol level.
+ 502 Protocol level already cur_level.
+ cur_level:
+ The current protocol level at which the server is running.
+ supported_level:
+ The maximum supported protocol level.
+
+
+Server sites:
+--------------
+Client command:
+-&gt; sites
+
+Server response:
+&lt;- code OK, site information follows (until terminating `.')
+&lt;- (data)
+&lt;- .
+
+ code:
+ 210 Ok, site information follows
+ 401 No site information available.
+
+ The data format is as follows:
+ site port latitude longitude description
+
+ The fields are as follows:
+ site:
+ The Internet address of the remote site.
+ port:
+ The port at which the server resides on that site.
+ latitude:
+ The latitude of the server site. The format is as follows:
+ CDDD.MM
+ Where "C" is the compass direction (N, S), "DDD" is the
+ degrees, and "MM" is the minutes.
+ longitude:
+ The longitude of the server site. Format is as above, except
+ the compass direction must be one of (E, W).
+ description:
+ A short description of the geographical location of the site.
+
+ Example:
+ cddb.moonsoft.com 888 N037.23 W122.01 Fremont, CA USA
+
+
+Server status:
+--------------
+Client command:
+-&gt; stat
+
+Server response:
+&lt;- code OK, status information follows (until terminating `.')
+&lt;- (data)
+&lt;- .
+
+ code:
+ 210 Ok, status information follows
+
+ The possible data is as follows:
+ current proto: &lt;current_level&gt;
+ An integer representing the server's current operating protocol
+ level.
+ max proto: &lt;max_level&gt;
+ The maximum supported protocol level.
+ gets: &lt;yes | no&gt;
+ Whether or not the client is allowed to get log information,
+ according to the string "yes" or "no".
+ updates: &lt;yes | no&gt;
+ Whether or not the client is allowed to initiate a database
+ update, according to the string "yes" or "no".
+ posting: &lt;yes | no&gt;
+ Whether or not the client is allowed to post new entries,
+ according to the string "yes" or "no".
+ quotes: &lt;yes | no&gt;
+ Whether or not quoted arguments are enabled, according to
+ the string "yes" or "no".
+ current users: &lt;num_users&gt;
+ The number of users currently connected to the server.
+ max users: &lt;num_max_users&gt;
+ The number of users that can concurrently connect to the server.
+ strip ext: &lt;yes | no&gt;
+ Whether or not extended data is stripped by the server before
+ presented to the user.
+ Database entries: &lt;num_db_entries&gt;
+ The total number of entries in the database.
+ Database entries by category:
+ This field is followed by a list of catgories and the number
+ of entries in that category. Each entry is of the following
+ format:
+
+ &lt;white space&gt;catgory: &lt;num_db_entries&gt;
+
+ The list of entries is terminated by the first line that does
+ not begin with white space.
+
+ Pending file transmissions:
+ This field is followed by a list of sites that are fed new
+ database entries at periodic intervals, and the number of
+ entries that have yet to be transmitted to that site.
+ Each entry is of the following format:
+
+ &lt;white space&gt;site: &lt;num_db_entries&gt;
+
+ The list of entries is terminated by the first line that does
+ not begin with white space.
+
+ This list may grow as needed, so clients must expect possible
+ unrecognizable data. Also, additional fields may be added to
+ the currently existing lines, although no existing fields will
+ be removed or change position.
+
+
+Server version:
+---------------
+Client command:
+-&gt; ver
+
+Server response:
+&lt;- code servername version copyright
+ or
+&lt;- code Version information follows
+
+ code:
+ 200 Version information.
+ 211 OK, version information follows (until terminating marker)
+ version:
+ Server version. Example: v1.0PL0
+ copyright:
+ Copyright string. Example: Copyright (c) 1996 Steve Scherf
+
+
+Database update:
+----------------
+Client command:
+-&gt; update
+
+Server response:
+&lt;- code Updating the database.
+ or
+&lt;- code Permission denied.
+ or
+&lt;- code Unable to update the database.
+
+ code:
+ 200 Updating the database.
+ 401 Permission denied.
+ 402 Unable to update the database.
+
+
+Server users:
+-------------
+Client command:
+-&gt; whom
+
+Server response:
+&lt;- code User list follows
+
+ code:
+ 210 OK, user list follows (until terminating marker)
+ 401 No user information available.
+
+
+Client sign-off:
+----------------
+Client command:
+-&gt; quit
+
+Server response:
+&lt;- code hostname closing connection. Goodbye.
+
+ code:
+ 230 OK, goodbye.
+ hostname:
+ Server host name. Example: xyz.fubar.com
+
+
+General errors:
+---------------
+
+Server response:
+&lt;- code error
+ code:
+ 402 Server error.
+ 408 CGI environment error.
+ 500 Command syntax error, command unknown, command unimplemented.
+ 530 Server error, server timeout.
+
+
+Reserved errors:
+----------------
+
+The following error codes are reserved, and will never be returned as a
+response to a CDDB protocol command. They are intended to be used internally
+by clients that have a need for generating pseudo-responses.
+
+ 600-699
+
+
+CDDB Protocol Level 2:
+----------------------
+
+In all respects, protocol level 2 is the same as level 1, with the exceptions
+listed below.
+
+Arguments to commands may be surrounded by double quotes. All characters
+within the quotes, including white space, are included in the argument. All
+white space is replaced by the `_' (2Dh) character by the server. White space
+is defined as ` ' (20h) and `^I' (control-I, or 09h).
+
+Arguments containing quotes that should not be interpreted with the special
+meaning described above should be escaped with a preceding backslash character,
+or '' (5Ch). If an actual backslash appears in an argument, it should be
+escaped with a preceding backslash. In both cases, the preceding backslash
+will be removed from the input before being interpreted.
+
+
+CDDB Protocol Level 3:
+----------------------
+
+Protocol level 3 is the same as level 2, with the exception listed below.
+
+The output of the "sites" command has changed to meet the folowing description:
+
+ The data format is as follows:
+ site protocol port address latitude longitude description
+
+ The fields are as follows:
+ site:
+ The Internet address of the remote site.
+ protocol:
+ The transfer protocol used to access the site.
+ port:
+ The port at which the server resides on that site.
+ address:
+ Any additional addressing information needed to access the
+ server. For example, for HTTP protocol servers, this would be
+ the path to the CDDB server CGI script. This field will be
+ "-" if no additional addressing information is needed.
+ latitude:
+ The latitude of the server site. The format is as follows:
+ CDDD.MM
+ Where "C" is the compass direction (N, S), "DDD" is the
+ degrees, and "MM" is the minutes.
+ longitude:
+ The longitude of the server site. Format is as above, except
+ the compass direction must be one of (E, W).
+ description:
+ A short description of the geographical location of the site.
+
+ Example:
+ cddb.moonsoft.com cddbp 888 - N037.23 W122.01 Fremont, CA USA
+ cddb.moonsoft.com http 80 /~cddb/cddb.cgi N037.23 W122.01 Fremont,CA USA
+
+Note that a site may appear once for each type of protocol it supports for
+accessing the server.
+
+
+Addendum A: Proper use of CDDBP:
+--------------------------------
+
+There are a few guidelines that must be followed in order to make proper use
+of CDDBP:
+
+- When handshaking with the server via the "cddb hello" command, the client
+ must specify its own name and version, not that of some other client (such
+ as xmcd). Also, the "username" and "hostname" must be that of the actual
+ user running the program, not some hardwired value.
+
+- Before performing a "cddb read", the client program MUST perform a
+ "cddb query". Failure to do so may result in the client program receiving
+ incorrect CDDB data from the server. Also, without performing a query, the
+ client program will not benefit from close matches in the event of the
+ lack of an exact match in the database.
+
+- For accounting purposes, it is best if client programs only perform a single
+ "cddb query" for a particular disc before performing a "cddb read" for that
+ disc.
+
+
+Addendum B: CDDBP under HTTP:
+-----------------------------
+
+Accessing a server as a CGI script is done in much the same way as through
+direct interaction. The command set is identical, though the method of
+communication is through CDDBP commands encapsulated in the HTTP protocol.
+The only limitation is that a single command may be executed per connection,
+since HTTP is not truly interactive. For the server to be accessed in this
+way, it must reside on the target host at a known URL which is accessible by
+the host HTTP server. The client program must connect to the HTTP server on
+the target host and issue an HTTP command with the appropriate CDDBP command
+encapsulated within.
+
+Commands may be submitted to servers in CGI mode using either the "GET" or
+"POST" HTTP commands. Both methods are supported, and there is no real
+difference between how both are to be used other than the syntactical
+difference between the two methods. The "POST" method may provide the ability
+to issue longer commands, though, depending on the architecture of the system
+on which the server resides.
+
+The server command must be sent as part of the "Request-URI" in the case
+of the "GET" method, and as the "Entity-Body" in the case of the "POST"
+method. In both cases, the command must be of the following form:
+
+cmd=server+command&amp;hello=joe+my.host.com+clientname+version&amp;proto=1
+
+Where the text following the "cmd=" represents the CDDBP command to be
+executed, the text following the "hello=" represents the arguments to
+the "cddb hello" command that is implied by this operation, and the
+text following the "proto=" represents the argument to the "proto" command
+that is implied by this operation.
+
+The "+" characters in the input represent spaces, and will be translated
+by the server before performing the request. Special characters may be
+represented by the sequence "%XX" where "XX" is a two-digit hex number
+corresponding to the ASCII (ISO-8859-1) sequence of that character. The
+"&amp;" characters denote separations between the command, hello and proto
+arguments. Newlines and carriage returns must not appear anywhere in the
+string except at the end.
+
+All CDDBP commands are supported under HTTP, except for "cddb hello",
+"cddb write", "proto" and "quit".
+
+For example, should user "joe" on system "my.host.com" be running xmcd 2.1,
+a read request for his currenly playing CD might look like this:
+
+cmd=cddb+read+rock+12345678&amp;hello=joe+my.host.com+xmcd+2.1&amp;proto=1
+
+The server will perform the implied "proto" and "cddb hello" commands,
+and then perform the requested "cddb read" command.
+
+Server response to the command is encapsulated in the HTTP server response,
+and appears in the "Entity-Body" exactly as it would appear using the CDDBP
+protocol. Note that the HTTP response "Entity-Header" is not guaranteed to
+contain a "Content-Length" field, so clients should be prepared to accept
+variable length input. This is no different from operation under CDDBP. The
+header will always contain a Mime "Content-Type" field which describes the
+body of data as "text/plain".
+
+For more detailed information on HTTP and Mime, see RFC 1945 and RFC 1521.
+</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
index 000000000..1bc9dbf66
--- /dev/null
+++ b/acme/bin/source/acd/discid
@@ -0,0 +1,159 @@
+
+CDDB DISCID
+-----------
+
+Both forms of CDDB access require that the software compute a "disc
+ID" which is an identifier that is used to access the CDDB. The disc
+ID is a 8-digit hexadecimal (base-16) number, computed using data from
+a CD's Table-of-Contents (TOC) in MSF (Minute Second Frame) form. The
+algorithm is listed below in Appendix A.
+
+It is crucial that your software compute the disc ID correctly. If it
+does not generate the correct disc ID, it will not be compatible with CDDB.
+Moreover, if your software submits CDDB entries with bad disc IDs to the
+CDDB archives, it could compromise the integrity of the CDDB.
+
+[...]
+
+APPENDIX A - CDDB DISCID ALGORITHM
+----------------------------------
+
+The following is a C code example that illustrates how to generate the
+CDDB disc ID. [...] A text description
+of the algorithm follows, which should contain the necessary information
+to code the algorithm in any programming language.
+
+
+struct toc {
+ int min;
+ int sec;
+ int frame;
+};
+
+struct toc cdtoc[100];
+
+int
+read_cdtoc_from_drive(void)
+{
+ /* Do whatever is appropriate to read the TOC of the CD
+ * into the cdtoc[] structure array.
+ */
+ return (tot_trks);
+}
+
+int
+cddb_sum(int n)
+{
+ int ret;
+
+ /* For backward compatibility this algorithm must not change */
+
+ ret = 0;
+
+ while (n > 0) {
+ ret = ret + (n % 10);
+ n = n / 10;
+ }
+
+ return (ret);
+}
+
+unsigned long
+cddb_discid(int tot_trks)
+{
+ int i,
+ t = 0,
+ n = 0;
+
+ /* For backward compatibility this algorithm must not change */
+
+ i = 0;
+
+ while (i < tot_trks) {
+ n = n + cddb_sum((cdtoc[i].min * 60) + cdtoc[i].sec);
+ i++;
+ }
+
+ t = ((cdtoc[tot_trks].min * 60) + cdtoc[tot_trks].sec) -
+ ((cdtoc[0].min * 60) + cdtoc[0].sec);
+
+ return ((n % 0xff) << 24 | t << 8 | tot_trks);
+}
+
+main()
+{
+ int tot_trks;
+
+ tot_trks = read_cdtoc_from_drive();
+ printf("The discid is %08x", cddb_discid(tot_trks));
+}
+
+
+This code assumes that your compiler and architecture support 32-bit
+integers.
+
+The cddb_discid function computes the discid based on the CD's TOC data
+in MSF form. The frames are ignored for this purpose. The function is
+passed a parameter of tot_trks (which is the total number of tracks on
+the CD), and returns the discid integer number.
+
+It is assumed that cdtoc[] is an array of data structures (records)
+containing the fields min, sec and frame, which are the minute, second
+and frame offsets (the starting location) of each track. This
+information is read from the TOC of the CD. There are actually
+tot_trks + 1 "active" elements in the array, the last one being the
+offset of the lead-out (also known as track 0xAA).
+
+The function loops through each track in the TOC, and for each track
+it takes the (M * 60) + S (total offset in seconds) of the track and
+feeds it to cddb_sum() function, which simply adds the value of each digit
+in the decimal string representation of the number. A running sum of this
+result for each track is kept in the variable n.
+
+At the end of the loop:
+1. t is calculated by subtracting the (M * 60) + S offset of the lead-out
+minus the (M * 60) + S offset of first track (yielding the length of
+the disc in seconds).
+
+2. The result of (n modulo FFh) is left-shifted by 24 bits.
+
+3. t is left shifted by 8.
+
+The bitwise-OR operation of result 2., 3. and the tot_trks number is
+used as the discid.
+
+The discid is represented in hexadecimal form for the purpose of
+xmcd cddb file names and the DISCID= field in the xmcd cddb file itself.
+If the hexadecimal string is less than 8 characters long, it is
+zero-padded to 8 characters (i.e., 3a8f07 becomes 003a8f07). All
+alpha characters in the string should be in lower case, where
+applicable.
+
+Important note for clients using the MS-Windows MCI interface:
+
+The Windows MCI interface does not provide the MSF location of the
+lead-out. Thus, you must compute the lead-out location by taking the
+starting position of the last track and add the length of the last track
+to it. However, the MCI interface returns the length of the last track
+as ONE FRAME SHORT of the actual length found in the CD's TOC. In most
+cases this does not affect the disc ID generated, because we truncate
+the frame count when computing the disc ID anyway. However, if the
+lead-out track has an actual a frame count of 0, the computed quantity
+(based on the MSF data returned from the MCI interface) would result in
+the seconds being one short and the frame count be 74. For example,
+a CD with the last track at an offset of 48m 32s 12f and having a
+track length of 2m 50s 63f has a lead-out offset of 51m 23s 0f. Windows
+MCI incorrectly reports the length as 2m 50s 62f, which would yield a
+lead-out offset of 51m 22s 74f, which causes the resulting truncated
+disc length to be off by one second. This will cause an incorrect disc
+ID to be generated. You should thus add one frame to the length of the
+last track when computing the location of the lead-out.
+
+The easiest way for Windows clients to compute the lead-out given information
+in MSF format is like this:
+
+(offset_minutes * 60 * 75) + (offset_seconds * 75) + offset_frames +
+(length_minutes * 60 * 75) + (length_seconds * 75) + length_frames + 1 = X
+
+Where X is the offset of the lead-out in frames. To find the lead-out in
+seconds, simply divide by 75 and discard the remainder.
diff --git a/acme/bin/source/acd/mailinglist b/acme/bin/source/acd/mailinglist
new file mode 100644
index 000000000..002fd8da8
--- /dev/null
+++ b/acme/bin/source/acd/mailinglist
@@ -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>
+<li><a href=topics.php>News-Topics</a>
+<li><a href=sections.php?op=listarticles&secid=1>About</a>
+<li><a href=sections.php?op=listarticles&secid=2>Developers</a>
+<li><a href=sections.php?op=listarticles&secid=3>Applications</a>
+<li><a href=sections.php?op=listarticles&secid=7>Download</a>
+<li><a href=forum/index.php>Forum</a>
+<li><a href=http://freedb.music.sk/search/>Web-based Search</a>
+<li><a href=links.php>Web Links</a>
+<li><a href=user.php>Your Account</a>
+<li><a href=submit.php>Submit News</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>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>
+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>
+<a href="mailto:info@freedb.org">info@freedb.org</a><hr>
+Databaseupdates:<br>
+<a href="mailto:updates@freedb.org">updates@freedb.org</a><br>
+(<b>NOT</b> for submission!)<hr>
+Please keep in mind that we are NOT the Nero-Support and please do not send CD-submits to the adresses above.<br>
+Submits have to go to:<br>
+<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:
+
+<h4>fdb-apps@freedb.org</h4>
+
+This mailinglist is intended for all developers that want to
+exchange tips and tricks, codesnippets and so on. Subcribe
+to this list by sending an email to<br>
+<a href="mailto:fdb-apps-request@freedb.org">fdb-apps-request@freedb.org</a><br>
+with the word "subscribe" in the body of the email.
+<h4>fdb-dev@freedb.org</h4>
+
+This list is for anyone interested in developing the freedb.org
+server software. Subcribe
+to this list by sending an email to<br>
+<a href="mailto:fdb-dev-request@freedb.org">fdb-dev-request@freedb.org</a><br>
+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
index 000000000..2ac388f56
--- /dev/null
+++ b/acme/bin/source/acd/main.c
@@ -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
index 000000000..7b549a6c5
--- /dev/null
+++ b/acme/bin/source/acd/mkfile
@@ -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
index 000000000..0a466c488
--- /dev/null
+++ b/acme/bin/source/acd/mmc.c
@@ -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
index 000000000..5ab337d9c
--- /dev/null
+++ b/acme/bin/source/acd/outline
@@ -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
index 000000000..49aaf1662
--- /dev/null
+++ b/acme/bin/source/acd/submit
@@ -0,0 +1,220 @@
+CDDB SUBMISSION
+---------------
+
+Your software may allow users to enter CDDB data and then submit them
+to the freedb archive.
+There are two methods of submission: <a href="#email">via e-mail</a> or <a href="#http">via http</a> using submit.cgi
+
+<a name="email"></a>1. Submission via e-mail
+------------------------
+
+Your software has to send the entry to the
+following address:
+
+ freedb-submit@freedb.org
+
+You may implement a button or somesuch in your software's user-interface
+to facilitate this. The destination e-mail address should be made
+user-configurable.
+
+There should be one e-mail message per freedb entry. The mail Subject
+line should be in the form "cddb category discid". For example:
+
+Subject: cddb rock 850f970b
+
+The body of the e-mail message should be in the format of a CDDB file
+entry as described <a href="http://freedb.freedb.org/software/old/DBFORMAT">here</a>. The messages should contain only
+plain ASCII text. Do not attach encoded information or add special
+escape sequences.
+
+Note that the disc ID specified in the mail Subject line should
+also appear in the list of disc IDs in the DISCID= field of the
+CDDB file entry. If not, it is considered an error and the submission
+will be rejected.
+
+You should only allow categories that are currently supported by the
+freedb (blues, classical, country, data, folk, jazz, misc, newage,
+reggae, rock, soundtrack). Submissions specifying unsupported
+categories will be rejected.
+
+Please do not allow a user to submit CD database entries that
+have completely unfilled contents (i.e., blank information in the
+disc artist/title as well as the track titles, or filled with
+useless default information like "track 1", "track 2", etc.).
+While the current CD database server checks and rejects submissions
+that have a blank DTITLE line, it doesn't (and can't feasibly) check
+the track titles effectively, nor can it check any of these fields
+if they are filled with a default string. If it were, it would
+have to be hacked to know about the default strings of every possible
+client.
+
+Thus, please design your client with this in mind. This is a somewhat
+tricky thing to do, as some CDs contain blank tracks with no titles
+and you need to allow for that. An example minimum requirement
+that a CD player client should meet is listed below:
+
+1. Don't allow the "send" or "submit" feature to be activated if
+ the CD database information form is not edited at all.
+2. Check that the disc artist/title contains something (that the user
+ typed in).
+3. Check that all of the tracks have a title filled in by the user
+ (some (but not all!) may be blank, but not the default string).
+
+This should minimize the number of useless garbage being submitted
+into the CD database.
+
+Before you release your software, please be sure that it produces
+submissions that adheres to the CDDB file format, and that the frame
+offset, disc length, and disc ID information are correctly computed.
+For testing, please make your software send submissions to the
+following e-mail address (rather than the real submission site at
+freedb-submit@freedb.org):
+
+ test-submit@freedb.org
+
+The test address performs sanity checking on the CDDB submission and
+sends back pass/fail confirmation, but does not actually deposit the
+entry in the CD database.
+
+<a name="http"></a>2. Submission via http
+----------------------
+
+For submit via http, your application has to transmit the entry to the
+database through a CGI program at the following URL:
+
+http://freedb.freedb.org/~cddb/submit.cgi
+
+Submissions are made through the CGI program as follows. You must only use
+the "POST" method of sending data; "GET" is not supported. There are several
+HTTP "Entity-Header" fields that must be included in the data followed by a
+blank line, followed by the "Entity-Body" (a.k.a the CDDB entry) in the
+format described in Appendix B below. The required header fields are:
+
+Category: CDDB_category
+Discid: CDDB_discid
+User-Email: user@domain
+Submit-Mode: test_or_submit
+Content-Length: length_of_CDDB_entry
+
+Where:
+
+- "CDDB_category" is one of the valid CDDB categories (blues, classical,
+ country, data, folk, jazz, misc, newage, reggae, rock, soundtrack).
+ Invalid categories will result in the entry being rejected.
+
+- "CDDB_discid" is the 8-digit hex CDDB disc ID of the entry as described in
+ 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
+ in the "DISCID=" section of the entry being submitted. If not, the entry
+ will be rejected.
+
+- "user@domain" is the valid email address of the user submitting the entry.
+ This is required in case a submission failure notice must be sent to the
+ user.
+
+- "test_or_submit" is the word "test" or "submit" (without the surrounding
+ quotes) to indicate whether the submission is a test submission or a real
+ submission to the database, respectively. See <a href="#testsubmission">below</a> for an explanation of
+ test submissions.
+
+- "length_of_CDDB_entry" is the size in bytes of the CDDB entry being
+ submitted. This number does not include the length of the header or the
+ blank line separating the HTTP header and the CDDB entry.
+
+There are several additional optional HTTP header fields that may also
+be specified (but which are currently not used by the freedb):
+
+Charset: character_set_of_CDDB_entry
+X-Cddbd-Note: message for user
+
+Where:
+
+- "character_set_of_CDDB_entry" is one of ISO-8859-1 or US-ASCII (lower case
+ may be used if desired). This specifies to the CDDB server which character
+ set the CDDB entry has been encoded in. If your application knows the
+ user's character set, then you should specify it here. Only these two
+ character sets are supported currently. DO NOT specify the character set
+ if your application does not have any way of verifying the user's character
+ set (i.e. do not guess; it's better not to specify it at all).
+
+- "message for user" is an arbitrary message to be included at the top of
+ any rejection notice that may be sent to the submitting user.
+
+An example submission showing the HTTP command, "Entity-Header" and "Entity-
+Body" follows:
+
+POST /~cddb/submit.cgi HTTP/1.0
+Category: rock
+Discid: 2a09310a
+User-Email: joe@joeshost.joesdomain.com
+Submit-Mode: submit
+Charset: ISO-8859-1
+X-Cddbd-Note: Problems with Super CD Player? Send email to support@supercd.com.
+Content-Length: 820
+
+# xmcd
+#
+# Track frame offsets:
+[ data omitted in this example for brevity ]
+PLAYORDER=
+
+Note the blank line between the "Content-Length" header field and the
+"# xmcd" which marks the beginning of the CDDB entry.
+
+When your application submits an entry through the CGI program, it will
+respond with a 3-digit response code indicating whether or not the entry has
+been forwarded to the freedb server for inclusion in the database, followed
+by a textual description of the response code. For example:
+
+200 OK, submission has been sent.
+400 Internal error: failed to forward submission.
+500 Missing required header information.
+
+These are but a few of the possible responses.
+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
+handling response codes.
+
+The body of the freedb entry being submitted should be sent verbatim as
+described in the <a href="http://freedb.freedb.org/software/old/DBFORMAT">database-format specification</a>. DO NOT encode the data in any
+way before transmitting it; data must be sent as raw text. For example,
+Windows programmers should not use the Windows URL encode function prior to
+calling the submit CGI program. Doing so may lead to corrupt data being sent
+and also possibly to rejected submissions.
+
+You may implement a button or somesuch in your software's user interface
+to initiate submissions. Rejected submissions are automatically returned
+via email to the sender specified in the "User-Email" header field with an
+explanation of the reason for the rejection.
+
+Please do not allow a user to submit CD database entries that
+have completely unfilled contents (i.e., blank information in the
+disc artist/title as well as the track titles, or filled with
+useless default information like "track 1", "track 2", etc.).
+While the current CD database server checks and rejects submissions
+that have a blank DTITLE line, it doesn't (and can't feasibly) check
+the track titles effectively, nor can it check any of these fields
+if they are filled with a default string. If it were, it would
+have to be hacked to know about the default strings of every possible
+client.
+
+Thus, please design your client with this in mind. This is a somewhat
+tricky thing to do, as some CDs contain blank tracks with no titles
+and you need to allow for that. An example minimum requirement
+that a CD player client should meet is listed below:
+
+1. Don't allow the "send" or "submit" feature to be activated if
+ the CD database information form is not edited at all.
+2. Check that the disc artist/title contains something (that the user
+ typed in).
+3. Check that all of the tracks have a title filled in by the user.
+ (some (but not all!) may be blank, but not the default string).
+
+Before you release your software, please be sure that it produces
+submissions that adhere to the CDDB file format, and that the frame
+offset, disc length, and disc ID information are correctly computed.
+For testing, please make your software send submissions with the
+"Submit-Mode" HTTP header field set to "test".
+
+<a name="testsubmission"></a>CDDB submissions sent in test mode will be sanity-checked by the freedb server
+and pass/fail confirmation sent back to the submitter, but will not actually
+be deposited in the CD database. Please DO NOT send submisions in "submit"
+mode until you have tested your program with several different CD's.
diff --git a/acme/bin/source/acd/toc.c b/acme/bin/source/acd/toc.c
new file mode 100644
index 000000000..c447949e6
--- /dev/null
+++ b/acme/bin/source/acd/toc.c
@@ -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
index 000000000..2b300fc12
--- /dev/null
+++ b/acme/bin/source/acd/util.c
@@ -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
index 000000000..2f378961c
--- /dev/null
+++ b/acme/bin/source/acd/win.c
@@ -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
index 000000000..f2c14f5ea
--- /dev/null
+++ b/acme/bin/source/adict/_adict.c
@@ -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
index 000000000..18657c572
--- /dev/null
+++ b/acme/bin/source/adict/_win.c
@@ -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
index 000000000..7ce53e5c2
--- /dev/null
+++ b/acme/bin/source/adict/adict.c
@@ -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
index 000000000..fffaa5454
--- /dev/null
+++ b/acme/bin/source/adict/adict.h
@@ -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
index 000000000..ad15bf079
--- /dev/null
+++ b/acme/bin/source/adict/man
@@ -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
index 000000000..14a079874
--- /dev/null
+++ b/acme/bin/source/adict/mkfile
@@ -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
index 000000000..e8b2ee257
--- /dev/null
+++ b/acme/bin/source/adict/win.c
@@ -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
index 000000000..8e1698aa1
--- /dev/null
+++ b/acme/bin/source/adict/win.h
@@ -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
index 000000000..48146338b
--- /dev/null
+++ b/acme/bin/source/mkfile
@@ -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
index 000000000..788e9d395
--- /dev/null
+++ b/acme/bin/source/mkwnew.c
@@ -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
index 000000000..02429785a
--- /dev/null
+++ b/acme/bin/source/spout.c
@@ -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
index 000000000..6791a21f2
--- /dev/null
+++ b/acme/bin/source/win/_fs.c
@@ -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
index 000000000..4ac9c19d5
--- /dev/null
+++ b/acme/bin/source/win/_main.c
@@ -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
index 000000000..38d31dcf0
--- /dev/null
+++ b/acme/bin/source/win/dat.h
@@ -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
index 000000000..6c41eb072
--- /dev/null
+++ b/acme/bin/source/win/fs.c
@@ -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
index 000000000..916f2b05f
--- /dev/null
+++ b/acme/bin/source/win/main.c
@@ -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
index 000000000..608f95b86
--- /dev/null
+++ b/acme/bin/source/win/mkfile
@@ -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
index 000000000..280f726c9
--- /dev/null
+++ b/acme/bin/source/win/pipe.c
@@ -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
index 000000000..ec8bd0c99
--- /dev/null
+++ b/acme/bin/source/win/util.c
@@ -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
index 000000000..2e7f883ed
--- /dev/null
+++ b/acme/bin/source/win/win.c
@@ -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
index 000000000..e69de29bb
--- /dev/null
+++ b/acme/bin/sparc/.dummy
diff --git a/acme/bin/sparc64/.dummy b/acme/bin/sparc64/.dummy
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/acme/bin/sparc64/.dummy
diff --git a/acme/bin/unind b/acme/bin/unind
new file mode 100755
index 000000000..6b1e59f35
--- /dev/null
+++ b/acme/bin/unind
@@ -0,0 +1,3 @@
+#!/bin/rc
+
+sed 's/^ //' $*
diff --git a/acme/bin/wnew b/acme/bin/wnew
new file mode 100755
index 000000000..f1362175d
--- /dev/null
+++ b/acme/bin/wnew
@@ -0,0 +1,5 @@
+#!/bin/rc -e
+
+id=`{mkwnew $*}
+cat >/mnt/acme/$id/body
+echo clean >/mnt/acme/$id/ctl