Browse Source

Plan 9 from Bell Labs 2002-12-12

David du Colombier 21 years ago
commit
c0f3d9569e
100 changed files with 10974 additions and 0 deletions
  1. 38 0
      29000/include/u.h
  2. 58 0
      29000/include/ureg.h
  3. 6 0
      29000/mkfile
  4. 9 0
      386/bin/ape/psh
  5. 5 0
      386/bin/auth/disable
  6. 5 0
      386/bin/auth/enable
  7. 36 0
      386/bin/auth/status
  8. 74 0
      386/bin/aux/cropmarks
  9. 64 0
      386/bin/aux/grabit
  10. 70 0
      386/bin/aux/hardcopy
  11. 14 0
      386/bin/aux/vmware
  12. 14 0
      386/bin/bitsy/light
  13. 20 0
      386/bin/disk/ksync
  14. 5 0
      386/bin/disk/mk9660
  15. 2 0
      386/bin/replica/changes
  16. 2 0
      386/bin/replica/pull
  17. 2 0
      386/bin/replica/push
  18. 2 0
      386/bin/replica/scan
  19. 2 0
      386/bin/replica/setupdirs
  20. 73 0
      386/include/ape/float.h
  21. 66 0
      386/include/ape/math.h
  22. 11 0
      386/include/ape/stdarg.h
  23. 33 0
      386/include/ape/ureg.h
  24. 60 0
      386/include/u.h
  25. 25 0
      386/include/ureg.h
  26. 6 0
      386/mkfile
  27. 60 0
      68000/include/u.h
  28. 28 0
      68000/include/ureg.h
  29. 7 0
      68000/mkfile
  30. 74 0
      68020/include/ape/float.h
  31. 65 0
      68020/include/ape/math.h
  32. 11 0
      68020/include/ape/stdarg.h
  33. 36 0
      68020/include/ape/ureg.h
  34. 14 0
      68020/include/dac.h
  35. 3 0
      68020/include/lbp.h
  36. 60 0
      68020/include/u.h
  37. 28 0
      68020/include/ureg.h
  38. 7 0
      68020/mkfile
  39. 38 0
      960/include/u.h
  40. 7 0
      960/mkfile
  41. 658 0
      LICENSE
  42. 2 0
      NOTICE
  43. 6 0
      acme/acid/Acid
  44. 3 0
      acme/acid/guide
  45. 30 0
      acme/bin/Battery
  46. 7 0
      acme/bin/Perl
  47. 3 0
      acme/bin/README
  48. 24 0
      acme/bin/adiff
  49. 3 0
      acme/bin/agrep
  50. 12 0
      acme/bin/ap
  51. 43 0
      acme/bin/aspell
  52. 4 0
      acme/bin/guide
  53. 3 0
      acme/bin/ind
  54. 10 0
      acme/bin/new
  55. 3 0
      acme/bin/quote
  56. 33 0
      acme/bin/source/acd/README
  57. 243 0
      acme/bin/source/acd/access
  58. 171 0
      acme/bin/source/acd/acd.h
  59. 347 0
      acme/bin/source/acd/acme.c
  60. 206 0
      acme/bin/source/acd/cddb
  61. 170 0
      acme/bin/source/acd/cddb.c
  62. 894 0
      acme/bin/source/acd/cddbproto
  63. 159 0
      acme/bin/source/acd/discid
  64. 220 0
      acme/bin/source/acd/mailinglist
  65. 135 0
      acme/bin/source/acd/main.c
  66. 22 0
      acme/bin/source/acd/mkfile
  67. 303 0
      acme/bin/source/acd/mmc.c
  68. 32 0
      acme/bin/source/acd/outline
  69. 220 0
      acme/bin/source/acd/submit
  70. 59 0
      acme/bin/source/acd/toc.c
  71. 89 0
      acme/bin/source/acd/util.c
  72. 320 0
      acme/bin/source/acd/win.c
  73. 584 0
      acme/bin/source/adict/adict.c
  74. 10 0
      acme/bin/source/adict/adict.h
  75. 26 0
      acme/bin/source/adict/man
  76. 11 0
      acme/bin/source/adict/mkfile
  77. 315 0
      acme/bin/source/adict/win.c
  78. 59 0
      acme/bin/source/adict/win.h
  79. 27 0
      acme/bin/source/mkfile
  80. 45 0
      acme/bin/source/mkwnew.c
  81. 123 0
      acme/bin/source/spout.c
  82. 95 0
      acme/bin/source/win/dat.h
  83. 146 0
      acme/bin/source/win/fs.c
  84. 654 0
      acme/bin/source/win/main.c
  85. 24 0
      acme/bin/source/win/mkfile
  86. 175 0
      acme/bin/source/win/pipe.c
  87. 90 0
      acme/bin/source/win/util.c
  88. 264 0
      acme/bin/source/win/win.c
  89. 3 0
      acme/bin/unind
  90. 9 0
      acme/mail/Mail
  91. 4 0
      acme/mail/guide
  92. 11 0
      acme/mail/mkbox
  93. 0 0
      acme/mail/old.iostats
  94. 55 0
      acme/mail/readme
  95. 164 0
      acme/mail/src/dat.h
  96. 75 0
      acme/mail/src/html.c
  97. 532 0
      acme/mail/src/mail.c
  98. 1306 0
      acme/mail/src/mesg.c
  99. 31 0
      acme/mail/src/mkfile
  100. 562 0
      acme/mail/src/reply.c

+ 38 - 0
29000/include/u.h

@@ -0,0 +1,38 @@
+#define nil		((void*)0)
+typedef	unsigned short	ushort;
+typedef	unsigned char	uchar;
+typedef	unsigned long	ulong;
+typedef	unsigned int	uint;
+typedef	signed char	schar;
+typedef	long long	vlong;
+typedef	unsigned long long uvlong;
+typedef	ushort		Rune;
+typedef 	union FPdbleword FPdbleword;
+typedef long	jmp_buf[2];
+#define	JMPBUFSP	0
+#define	JMPBUFPC	1
+#define	JMPBUFDPC	0
+typedef unsigned int	mpdigit;	/* for /sys/include/mp.h */
+typedef unsigned int	u32int;
+union FPdbleword
+{
+	double	x;
+	struct {	/* big endian */
+		ulong hi;
+		ulong lo;
+	};
+};
+
+typedef	char*	va_list;
+#define va_start(list, start) list =\
+	(sizeof(start) < 4?\
+		(char*)((int*)&(start)+1):\
+		(char*)(&(start)+1))
+#define va_end(list)\
+	USED(list)
+#define va_arg(list, mode)\
+	((sizeof(mode) == 1)?\
+		((mode*)(list += 4))[-1]:\
+	(sizeof(mode) == 2)?\
+		((mode*)(list += 4))[-1]:\
+		((mode*)(list += sizeof(mode)))[-1])

+ 58 - 0
29000/include/ureg.h

@@ -0,0 +1,58 @@
+struct Ureg
+{
+	ulong	cause;			/* trap number */
+	ulong	status;			/* old status */
+	ulong	cha;			/* channel address */
+	ulong	chd;			/* channel data */
+	ulong	chc;			/* channel control */
+	union{
+		ulong	pc0;
+		ulong	pc;
+	};
+	ulong	pc1;
+	ulong	pc2;
+	ulong	ipc;
+	ulong	ipa;
+	ulong	ipb;
+	ulong	q;
+	ulong	alustat;
+	ulong	cr;
+	ulong	r64;
+	ulong	sp;			/* r65 */
+	ulong	r66;
+	ulong	r67;
+	ulong	r68;
+	ulong	r69;
+	ulong	r70;
+	ulong	r71;
+	ulong	r72;
+	ulong	r73;
+	ulong	r74;
+	ulong	r75;
+	ulong	r76;
+	ulong	r77;
+	ulong	r78;
+	ulong	r79;
+	ulong	r80;
+	ulong	r81;
+	ulong	r82;
+	ulong	r83;
+	ulong	r84;
+	ulong	r85;
+	ulong	r86;
+	ulong	r87;
+	ulong	r88;
+	ulong	r89;
+	ulong	r90;
+	ulong	r91;
+	ulong	r92;
+	ulong	r93;
+	ulong	r94;
+	ulong	r95;
+	ulong	r96;
+	ulong	r97;
+	ulong	r98;
+	ulong	r99;
+	ulong	r100;
+	ulong	r101;
+};

+ 6 - 0
29000/mkfile

@@ -0,0 +1,6 @@
+</sys/src/mkfile.proto
+
+CC=9c
+LD=9l
+O=9
+AS=9a

+ 9 - 0
386/bin/ape/psh

@@ -0,0 +1,9 @@
+#!/bin/rc
+# set up a shell running in an approximate POSIX 1003.2 environment
+rfork en
+fn sigterm{}
+HOME=$home
+bind -b /rc/bin/ape /bin
+bind -b /$cputype/bin/ape /bin
+bind -a /$cputype/bin/pub /bin
+/bin/sh $*

+ 5 - 0
386/bin/auth/disable

@@ -0,0 +1,5 @@
+#!/bin/rc
+if(test -e /mnt/keys/$1)
+	echo -n disabled > /mnt/keys/$1/status
+if(test -e /mnt/netkeys/$1)
+	echo -n disabled > /mnt/netkeys/$1/status

+ 5 - 0
386/bin/auth/enable

@@ -0,0 +1,5 @@
+#!/bin/rc
+if(test -e /mnt/keys/$1)
+	echo -n ok > /mnt/keys/$1/status
+if(test -e /mnt/netkeys/$1)
+	echo -n ok > /mnt/netkeys/$1/status

+ 36 - 0
386/bin/auth/status

@@ -0,0 +1,36 @@
+#!/bin/rc
+cd /mnt/keys/$1 > /dev/null >[2=1] && {
+	stat=`{cat status}
+	exp=`{cat expire}
+	switch($exp){
+	case never 0
+		exp='never expires'
+	case *
+		exp=(expires on `{date $exp})
+	}
+	switch($stat){
+	case expired
+		echo user $1: plan 9 key has expired
+	case *
+		echo user $1: plan 9 key status is $stat and $exp
+	}
+	grep '^'$1'[ 	]' /adm/keys.who | tail -1
+}
+cd /mnt/netkeys/$1 > /dev/null >[2=1] && {
+	stat=`{cat status}
+	exp=`{cat expire}
+	switch($exp){
+	case never 0
+		exp='never expires'
+	case *
+		exp=(expires on `{date $exp})
+	}
+	switch($stat){
+	case expired
+		echo user $1: network key has expired
+	case *
+		echo user $1: network key status is $stat and $exp
+		auth/printnetkey $1
+	}
+	grep '^'$1'[ 	]' /adm/netkeys.who | tail -1
+}

+ 74 - 0
386/bin/aux/cropmarks

@@ -0,0 +1,74 @@
+#!/bin/rc
+# Center pages and put cropmarks at each corner. Physical page size
+# is set with -w and -h. The default is 8.5 by 11.0 inches. Device
+# dependent code to change paper size (e.g. with setpageparams) goes
+# in the prologue. You may need to customize the device dependent
+# code that we distribute. By default it only supports variable page
+# sizes on Linotronic typesetters, and assumes those typesetters are
+# using 12 inch wide paper. Use -d to disable execution of device
+# dependent PostScript code.
+#
+# What's here was written quickly and will likely be very different
+# in our next release. It should be part of a more general program!!
+# 
+
+POSTLIB=/sys/lib/postscript/prologues
+PROLOGUE=$POSTLIB/cropmarks.ps
+
+EXPANDPAGE=true
+PAGEWIDTH=8.5
+PAGEHEIGHT=11.0
+SCALETOFIT=false
+XOFFSET=0.0
+YOFFSET=0.0
+
+NONCONFORMING=%!PS
+ENDPROLOG=%%EndProlog
+BEGINSETUP=%%BeginSetup
+ENDSETUP=%%EndSetup
+
+while (! ~ $#* 0 && ~ $1 -*) {
+	switch ($1) {
+	case -d;  EXPANDPAGE=false
+
+	case -h;  shift; PAGEHEIGHT=$1
+	case -h*; PAGEHEIGHT=`{echo $1 | sed s/-h//}
+
+	case -s;  SCALETOFIT=true
+
+	case -w;  shift; PAGEWIDTH=$1
+	case -w*; PAGEWIDTH=`{echo $1 | sed s/-w//}
+
+	case -x;  shift; XOFFSET=$1
+	case -x*; XOFFSET=`{echo $1 | sed s/-x//}
+
+	case -y;  shift; YOFFSET=$1
+	case -y*; YOFFSET=`{echo $1 | sed s/-y//}
+
+	case -L;  shift; PROLOGUE=$1
+	case -L*; PROLOGUE=`{echo $1 | sed s/-L//}
+
+	case --;
+
+	case -*;  echo '$0: illegal option $1' >[1=2]; exit 1
+
+	}
+	shift
+}
+
+echo $NONCONFORMING
+cat $PROLOGUE
+echo $ENDPROLOG
+echo $BEGINSETUP
+echo 'CropmarkDict begin'
+echo '/pageheight '$PAGEHEIGHT' def'
+echo '/pagewidth '$PAGEWIDTH' def'
+echo '/expandpage '$EXPANDPAGE' def'
+echo '/scaletofit '$SCALETOFIT' def'
+echo '/xoffset '$XOFFSET' def'
+echo '/yoffset '$YOFFSET' def'
+echo 'setup'
+echo 'end'
+echo $ENDSETUP
+
+cat $*

+ 64 - 0
386/bin/aux/grabit

@@ -0,0 +1,64 @@
+#!/bin/rc
+# Print a listing of an object, often a dictionary or an array. Something
+# like ==, but the output is often easier to read and closer to PostScript
+# that can be sent back through the interpreter.
+#
+
+POSTLIB=/sys/lib/postscript/prologues
+PROLOGUE=$POSTLIB/grabit.ps
+
+COPYFILE=
+RECURSIVE=true
+OMITNAMES='/Grabit /GrabitDict'
+
+NONCONFORMING='%!PS'
+ENDPROLOG='%%EndProlog'
+BEGINSETUP='%%BeginSetup'
+ENDSETUP='%%EndSetup'
+TRAILER='%%Trailer'
+
+SETUP=GrabitSetup
+
+while (! ~ $#* 0 && ~ $1 -*) {
+	switch ($1) {
+	case -d;  RECURSIVE=false
+
+	case -o;  shift; OMITNAMES=$OMITNAMES' '$1
+	case -o*; OMITNAMES=$OMITNAMES' '`{echo $1 | sed s/-o//}
+
+	case -C;  shift; COPYFILE=$COPYFILE' '$1
+	case -C*; COPYFILE=$COPYFILE' '`{echo $1 | sed s/-C//}
+
+	case -L;  shift; PROLOGUE=$1
+	case -L*; PROLOGUE=`{echo $1 | sed s/-L//}
+
+	case --;
+
+	case -*;  echo $0: illegal option $1 >[1=2]; exit 1
+    }
+    shift
+done
+
+echo $NONCONFORMING
+cat $PROLOGUE
+echo $ENDPROLOG
+echo $BEGINSETUP
+if (~ $COPYFILE '') COPYFILE=/dev/null
+cat $COPYFILE
+echo 'GrabitDict begin'
+echo '/recursive '$RECURSIVE' def'
+
+echo mark
+for (i in $OMITNAMES) {
+	switch ($i) {
+	case /*; echo $i
+	case ?*; echo /$i
+	}
+}
+echo GrabitSetup
+
+echo end
+echo $ENDSETUP
+
+for (i) echo $i Grabit
+

+ 70 - 0
386/bin/aux/hardcopy

@@ -0,0 +1,70 @@
+#!/bin/rc
+# Generate paper output from the data that a PostScript program normally
+# sends back to a host computer using file output operators.
+#
+
+POSTLIB=/sys/lib/postscript/prologues
+PROLOGUE=$POSTLIB/hardcopy.ps
+
+OPTIONS=
+MODE=portrait
+
+NONCONFORMING='%!PS'
+ENDPROLOG='%%EndProlog'
+BEGINSETUP='%%BeginSetup'
+ENDSETUP='%%EndSetup'
+TRAILER='%%Trailer'
+
+SETUP=HardcopySetup
+DONE='(%stdout)(w) file -1 write'
+
+while (! ~ $#* 0 && ~ $1 -*) {
+	switch ($1) {
+	case -c;  shift; OPTIONS=$OPTIONS' /#copies '$1' store'
+	case -c*; OPTIONS=$OPTIONS' /#copies `{echo $1 | sed s/-c//}' store'
+
+	case -f;  shift; OPTIONS=$OPTIONS' /font '/$1' def'
+	case -f*; OPTIONS=$OPTIONS' /font '/`{echo $1 | sed s/-f//}' def'
+
+	case -p;  shift; MODE=$1
+	case -p*; MODE=`{echo $1 | sed s/-p//}
+
+	case -m;  shift; OPTIONS=$OPTIONS' /magnification '$1' def'
+	case -m*; OPTIONS=$OPTIONS' /magnification '`{echo $1 | sed s/-m//}' def'
+
+	case -s;  shift; OPTIONS=$OPTIONS' /pointsize '$1' def'
+	case -s*; OPTIONS=$OPTIONS' /pointsize '`{echo $1 | sed s/-s//}' def'
+
+	case -x;  shift; OPTIONS=$OPTIONS' /xoffset '$1' def'
+	case -x*; OPTIONS=$OPTIONS' /xoffset '`{echo $1 | sed s/-x//}' def'
+
+	case -y;  shift; OPTIONS=$OPTIONS' /yoffset '$1' def'
+	case -y*; OPTIONS=$OPTIONS' /yoffset '`{echo $1 | sed s/-y//}' def'
+
+	case -L;  shift; PROLOGUE=$1
+	case -L*; PROLOGUE=`{echo $1 | sed s/-L//}
+
+	case --;
+
+	case -*;  echo '$0: illegal option $1' >&2; exit 1
+	}
+	shift
+}
+
+switch ($MODE) {
+	case l*; OPTIONS=$OPTIONS' /landscape true def'
+	case *;  OPTIONS=$OPTIONS' /landscape false def'
+}
+
+echo $NONCONFORMING
+cat $PROLOGUE
+echo $ENDPROLOG
+echo $BEGINSETUP
+echo $OPTIONS
+echo $SETUP
+echo $ENDSETUP
+
+cat $*
+
+echo $TRAILER
+echo $DONE

+ 14 - 0
386/bin/aux/vmware

@@ -0,0 +1,14 @@
+#!/bin/rc
+
+if(aux/isvmware -s){
+	echo -n off >'#P/i8253timerset'
+	for(i in '#S'/sd??)
+		if(test -f $i/ctl)
+			echo dma on >$i/ctl
+	aux/vmwarefs
+	if(! test -f /dev/snarf)
+		aux/stub /dev/snarf
+	bind /mnt/vmware/snarf /dev/snarf
+	aux/vmmousepoll &
+}
+

+ 14 - 0
386/bin/bitsy/light

@@ -0,0 +1,14 @@
+#!/bin/rc
+
+#master file is /sys/src/cmd/bitsy/light.rc
+
+switch($1){
+case on
+	echo -n 2 1 0x80 > /dev/backlight
+case off
+	echo -n 2 0 0x80 > /dev/backlight
+case auto
+	echo -n 1 1 0x80 > /dev/backlight
+case *
+	echo -n 2 1 $1 > /dev/backlight
+}

+ 20 - 0
386/bin/disk/ksync

@@ -0,0 +1,20 @@
+#!/bin/rc
+
+fn dosync {
+	echo disk/kfscmd $* sync
+	if(disk/kfscmd $* sync)
+		echo synced
+	if not
+		echo sync failed
+}
+
+kfs = `{ls /srv/kfs.*.cmd >[2]/dev/null}
+switch($#kfs){
+case 0
+	dosync
+case *
+	if(test -r /srv/kfs.cmd)
+		dosync
+	for(i in $kfs)
+		dosync -n `{echo $i|sed 's%/srv/kfs.(.*).cmd%\1%'}
+}

+ 5 - 0
386/bin/disk/mk9660

@@ -0,0 +1,5 @@
+#!/bin/rc
+
+# the master copy of this file is /sys/src/cmd/disk/9660/mk9660.rc
+# do NOT edit the copies in /*/bin/disk.
+exec disk/dump9660 -M $*

+ 2 - 0
386/bin/replica/changes

@@ -0,0 +1,2 @@
+#!/bin/rc
+exec /rc/bin/replica/changes $*

+ 2 - 0
386/bin/replica/pull

@@ -0,0 +1,2 @@
+#!/bin/rc
+exec /rc/bin/replica/pull $*

+ 2 - 0
386/bin/replica/push

@@ -0,0 +1,2 @@
+#!/bin/rc
+exec /rc/bin/replica/push $*

+ 2 - 0
386/bin/replica/scan

@@ -0,0 +1,2 @@
+#!/bin/rc
+exec /rc/bin/replica/scan $*

+ 2 - 0
386/bin/replica/setupdirs

@@ -0,0 +1,2 @@
+#!/bin/rc
+exec /rc/bin/replica/setupdirs $*

+ 73 - 0
386/include/ape/float.h

@@ -0,0 +1,73 @@
+#ifndef __FLOAT
+#define __FLOAT
+/* IEEE, default rounding */
+
+#define FLT_ROUNDS	1
+#define FLT_RADIX	2
+
+#define FLT_DIG		6
+#define FLT_EPSILON	1.19209290e-07
+#define FLT_MANT_DIG	24
+#define FLT_MAX		3.40282347e+38
+#define FLT_MAX_10_EXP	38
+#define FLT_MAX_EXP	128
+#define FLT_MIN		1.17549435e-38
+#define FLT_MIN_10_EXP	-37
+#define FLT_MIN_EXP	-125
+
+#define DBL_DIG		15
+#define DBL_EPSILON	2.2204460492503131e-16
+#define DBL_MANT_DIG	53
+#define DBL_MAX		1.797693134862315708145e+308
+#define DBL_MAX_10_EXP	308
+#define DBL_MAX_EXP	1024
+#define DBL_MIN		2.225073858507201383090233e-308
+#define DBL_MIN_10_EXP	-307
+#define DBL_MIN_EXP	-1021
+#define LDBL_MANT_DIG	DBL_MANT_DIG
+#define LDBL_EPSILON	DBL_EPSILON
+#define LDBL_DIG	DBL_DIG
+#define LDBL_MIN_EXP	DBL_MIN_EXP
+#define LDBL_MIN	DBL_MIN
+#define LDBL_MIN_10_EXP	DBL_MIN_10_EXP
+#define LDBL_MAX_EXP	DBL_MAX_EXP
+#define LDBL_MAX	DBL_MAX
+#define LDBL_MAX_10_EXP	DBL_MAX_10_EXP
+
+typedef 	union FPdbleword FPdbleword;
+union FPdbleword
+{
+	double	x;
+	struct {	/* little endian */
+		long lo;
+		long hi;
+	};
+};
+
+#ifdef _RESEARCH_SOURCE
+/* define stuff needed for floating conversion */
+#define IEEE_8087	1
+#define Sudden_Underflow 1
+#endif
+#ifdef _PLAN9_SOURCE
+/* FCR */
+#define	FPINEX	(1<<5)
+#define	FPOVFL	(1<<3)
+#define	FPUNFL	((1<<4)|(1<<1))
+#define	FPZDIV	(1<<2)
+#define	FPRNR	(0<<10)
+#define	FPRZ	(3<<10)
+#define	FPRPINF	(2<<10)
+#define	FPRNINF	(1<<10)
+#define	FPRMASK	(3<<10)
+#define	FPPEXT	(3<<8)
+#define	FPPSGL	(0<<8)
+#define	FPPDBL	(2<<8)
+#define	FPPMASK	(3<<8)
+/* FSR */
+#define	FPAINEX	FPINEX
+#define	FPAOVFL	FPOVFL
+#define	FPAUNFL	FPUNFL
+#define	FPAZDIV	FPZDIV
+#endif
+#endif /* __FLOAT */

+ 66 - 0
386/include/ape/math.h

@@ -0,0 +1,66 @@
+#ifndef __MATH
+#define __MATH
+#pragma lib "/$M/lib/ape/libap.a"
+
+/* a HUGE_VAL appropriate for IEEE double-precision */
+/* the correct value, 1.797693134862316e+308, causes a ken overflow */
+#define HUGE_VAL 1.79769313486231e+308
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern double acos(double);
+extern double asin(double);
+extern double atan(double);
+extern double atan2(double, double);
+extern double cos(double);
+extern double sin(double);
+extern double tan(double);
+extern double cosh(double);
+extern double sinh(double);
+extern double tanh(double);
+extern double exp(double);
+extern double frexp(double, int *);
+extern double ldexp(double, int);
+extern double log(double);
+extern double log10(double);
+extern double modf(double, double *);
+extern double pow(double, double);
+extern double sqrt(double);
+extern double ceil(double);
+extern double fabs(double);
+extern double floor(double);
+extern double fmod(double, double);
+extern double NaN(void);
+extern int isNaN(double);
+extern double Inf(int);
+extern int isInf(double, int);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#ifdef _RESEARCH_SOURCE
+/* does >> treat left operand as unsigned ? */
+#define Unsigned_Shifts 1
+#define	M_E		2.7182818284590452354	/* e */
+#define	M_LOG2E		1.4426950408889634074	/* log 2e */
+#define	M_LOG10E	0.43429448190325182765	/* log 10e */
+#define	M_LN2		0.69314718055994530942	/* log e2 */
+#define	M_LN10		2.30258509299404568402	/* log e10 */
+#define	M_PI		3.14159265358979323846	/* pi */
+#define	M_PI_2		1.57079632679489661923	/* pi/2 */
+#define	M_PI_4		0.78539816339744830962	/* pi/4 */
+#define	M_1_PI		0.31830988618379067154	/* 1/pi */
+#define	M_2_PI		0.63661977236758134308	/* 2/pi */
+#define	M_2_SQRTPI	1.12837916709551257390	/* 2/sqrt(pi) */
+#define	M_SQRT2		1.41421356237309504880	/* sqrt(2) */
+#define	M_SQRT1_2	0.70710678118654752440	/* 1/sqrt(2) */
+
+
+extern double hypot(double, double);
+#endif
+#endif /* __MATH */

+ 11 - 0
386/include/ape/stdarg.h

@@ -0,0 +1,11 @@
+#ifndef __STDARG
+#define __STDARG
+
+typedef char *va_list;
+
+#define va_start(list, start) list = (sizeof(start)<4 ? (char *)((int *)&(start)+1) : \
+(char *)(&(start)+1))
+#define va_end(list)
+#define va_arg(list, mode) ((mode*)(list += sizeof(mode)))[-1]
+
+#endif /* __STDARG */

+ 33 - 0
386/include/ape/ureg.h

@@ -0,0 +1,33 @@
+#ifndef __UREG_H
+#define __UREG_H
+#if !defined(_PLAN9_SOURCE)
+    This header file is an extension to ANSI/POSIX
+#endif
+
+struct Ureg
+{
+	unsigned long	di;		/* general registers */
+	unsigned long	si;		/* ... */
+	unsigned long	bp;		/* ... */
+	unsigned long	nsp;
+	unsigned long	bx;		/* ... */
+	unsigned long	dx;		/* ... */
+	unsigned long	cx;		/* ... */
+	unsigned long	ax;		/* ... */
+	unsigned long	gs;		/* data segments */
+	unsigned long	fs;		/* ... */
+	unsigned long	es;		/* ... */
+	unsigned long	ds;		/* ... */
+	unsigned long	trap;		/* trap type */
+	unsigned long	ecode;		/* error code (or zero) */
+	unsigned long	pc;		/* pc */
+	unsigned long	cs;		/* old context */
+	unsigned long	flags;		/* old flags */
+	union {
+		unsigned long	usp;
+		unsigned long	sp;
+	};
+	unsigned long	ss;		/* old stack segment */
+};
+
+#endif

+ 60 - 0
386/include/u.h

@@ -0,0 +1,60 @@
+#define nil		((void*)0)
+typedef	unsigned short	ushort;
+typedef	unsigned char	uchar;
+typedef unsigned long	ulong;
+typedef unsigned int	uint;
+typedef   signed char	schar;
+typedef	long long	vlong;
+typedef	unsigned long long uvlong;
+typedef	ushort		Rune;
+typedef 	union FPdbleword FPdbleword;
+typedef long	jmp_buf[2];
+#define	JMPBUFSP	0
+#define	JMPBUFPC	1
+#define	JMPBUFDPC	0
+typedef unsigned int	mpdigit;	/* for /sys/include/mp.h */
+typedef unsigned int	u32int;
+
+/* FCR */
+#define	FPINEX	(1<<5)
+#define	FPUNFL	((1<<4)|(1<<1))
+#define	FPOVFL	(1<<3)
+#define	FPZDIV	(1<<2)
+#define	FPINVAL	(1<<0)
+#define	FPRNR	(0<<10)
+#define	FPRZ	(3<<10)
+#define	FPRPINF	(2<<10)
+#define	FPRNINF	(1<<10)
+#define	FPRMASK	(3<<10)
+#define	FPPEXT	(3<<8)
+#define	FPPSGL	(0<<8)
+#define	FPPDBL	(2<<8)
+#define	FPPMASK	(3<<8)
+/* FSR */
+#define	FPAINEX	FPINEX
+#define	FPAOVFL	FPOVFL
+#define	FPAUNFL	FPUNFL
+#define	FPAZDIV	FPZDIV
+#define	FPAINVAL	FPINVAL
+union FPdbleword
+{
+	double	x;
+	struct {	/* little endian */
+		ulong lo;
+		ulong hi;
+	};
+};
+
+typedef	char*	va_list;
+#define va_start(list, start) list =\
+	(sizeof(start) < 4?\
+		(char*)((int*)&(start)+1):\
+		(char*)(&(start)+1))
+#define va_end(list)\
+	USED(list)
+#define va_arg(list, mode)\
+	((sizeof(mode) == 1)?\
+		((mode*)(list += 4))[-4]:\
+	(sizeof(mode) == 2)?\
+		((mode*)(list += 4))[-2]:\
+		((mode*)(list += sizeof(mode)))[-1])

+ 25 - 0
386/include/ureg.h

@@ -0,0 +1,25 @@
+struct Ureg
+{
+	ulong	di;		/* general registers */
+	ulong	si;		/* ... */
+	ulong	bp;		/* ... */
+	ulong	nsp;
+	ulong	bx;		/* ... */
+	ulong	dx;		/* ... */
+	ulong	cx;		/* ... */
+	ulong	ax;		/* ... */
+	ulong	gs;		/* data segments */
+	ulong	fs;		/* ... */
+	ulong	es;		/* ... */
+	ulong	ds;		/* ... */
+	ulong	trap;		/* trap type */
+	ulong	ecode;		/* error code (or zero) */
+	ulong	pc;		/* pc */
+	ulong	cs;		/* old context */
+	ulong	flags;		/* old flags */
+	union {
+		ulong	usp;
+		ulong	sp;
+	};
+	ulong	ss;		/* old stack segment */
+};

+ 6 - 0
386/mkfile

@@ -0,0 +1,6 @@
+</sys/src/mkfile.proto
+
+CC=8c
+LD=8l
+O=8
+AS=8a

+ 60 - 0
68000/include/u.h

@@ -0,0 +1,60 @@
+#define nil		((void*)0)
+typedef	unsigned short	ushort;
+typedef	unsigned char	uchar;
+typedef	unsigned long	ulong;
+typedef	unsigned int	uint;
+typedef	signed char	schar;
+typedef	long long	vlong;
+typedef	unsigned long long uvlong;
+typedef	ushort		Rune;
+typedef 	union FPdbleword FPdbleword;
+typedef long	jmp_buf[2];
+#define	JMPBUFSP	0
+#define	JMPBUFPC	1
+#define	JMPBUFDPC	0
+typedef unsigned int	mpdigit;	/* for /sys/include/mp.h */
+typedef unsigned int	u32int;
+
+/* FCR */
+#define	FPINEX	(3<<8)
+#define	FPOVFL	(1<<12)
+#define	FPUNFL	(1<<11)
+#define	FPZDIV	(1<<10)
+#define	FPRNR	(0<<4)
+#define	FPRZ	(1<<4)
+#define	FPINVAL	(3<<13)
+#define	FPRPINF	(3<<4)
+#define	FPRNINF	(2<<4)
+#define	FPRMASK	(3<<4)
+#define	FPPEXT	(0<<6)
+#define	FPPSGL	(1<<6)
+#define	FPPDBL	(2<<6)
+#define	FPPMASK	(3<<6)
+/* FSR */
+#define	FPAINEX	FPINEX
+#define	FPAOVFL	FPOVFL
+#define	FPAUNFL	FPUNFL
+#define	FPAZDIV	FPZDIV
+#define	FPAINVAL	FPINVAL
+union FPdbleword
+{
+	double	x;
+	struct {	/* big endian */
+		ulong hi;
+		ulong lo;
+	};
+};
+
+typedef	char*	va_list;
+#define va_start(list, start) list =\
+	(sizeof(start) < 4?\
+		(char*)((int*)&(start)+1):\
+		(char*)(&(start)+1))
+#define va_end(list)\
+	USED(list)
+#define va_arg(list, mode)\
+	((sizeof(mode) == 1)?\
+		((mode*)(list += 4))[-1]:\
+	(sizeof(mode) == 2)?\
+		((mode*)(list += 4))[-1]:\
+		((mode*)(list += sizeof(mode)))[-1])

+ 28 - 0
68000/include/ureg.h

@@ -0,0 +1,28 @@
+struct Ureg
+{
+	ulong	r0;
+	ulong	r1;
+	ulong	r2;
+	ulong	r3;
+	ulong	r4;
+	ulong	r5;
+	ulong	r6;
+	ulong	r7;
+	ulong	a0;
+	ulong	a1;
+	ulong	a2;
+	ulong	a3;
+	ulong	a4;
+	ulong	a5;
+	ulong	a6;
+	ulong	sp;
+	ulong	usp;
+	ulong	magic;		/* for db to find bottom of ureg */
+	ushort	sr;
+	ulong	pc;
+	ushort	vo;
+#ifndef	UREGVARSZ
+#define	UREGVARSZ 23		/* for 68040; 15 is enough on 68020 */
+#endif
+	uchar	microstate[UREGVARSZ];	/* variable-sized portion */
+};

+ 7 - 0
68000/mkfile

@@ -0,0 +1,7 @@
+</sys/src/mkfile.proto
+
+CC=1c
+LD=1l
+O=1
+RL=rl
+AS=1a

+ 74 - 0
68020/include/ape/float.h

@@ -0,0 +1,74 @@
+#ifndef __FLOAT
+#define __FLOAT
+/* IEEE, default rounding */
+
+#define FLT_ROUNDS	1
+#define FLT_RADIX	2
+
+#define FLT_DIG		6
+#define FLT_EPSILON	1.19209290e-07
+#define FLT_MANT_DIG	24
+#define FLT_MAX		3.40282347e+38
+#define FLT_MAX_10_EXP	38
+#define FLT_MAX_EXP	128
+#define FLT_MIN		1.17549435e-38
+#define FLT_MIN_10_EXP	-37
+#define FLT_MIN_EXP	-125
+
+#define DBL_DIG		15
+#define DBL_EPSILON	2.2204460492503131e-16
+#define DBL_MANT_DIG	53
+#define DBL_MAX		1.797693134862315708145e+308
+#define DBL_MAX_10_EXP	308
+#define DBL_MAX_EXP	1024
+#define DBL_MIN		2.225073858507201383090233e-308
+#define DBL_MIN_10_EXP	-307
+#define DBL_MIN_EXP	-1021
+#define LDBL_MANT_DIG	DBL_MANT_DIG
+#define LDBL_EPSILON	DBL_EPSILON
+#define LDBL_DIG	DBL_DIG
+#define LDBL_MIN_EXP	DBL_MIN_EXP
+#define LDBL_MIN	DBL_MIN
+#define LDBL_MIN_10_EXP	DBL_MIN_10_EXP
+#define LDBL_MAX_EXP	DBL_MAX_EXP
+#define LDBL_MAX	DBL_MAX
+#define LDBL_MAX_10_EXP	DBL_MAX_10_EXP
+
+
+typedef 	union FPdbleword FPdbleword;
+union FPdbleword
+{
+	double	x;
+	struct {	/* big endian */
+		long hi;
+		long lo;
+	};
+};
+
+#ifdef _RESEARCH_SOURCE
+/* define order of longs in IEEE double: little endian */
+#define IEEE_MC68k	1
+#define Sudden_Underflow 1
+#endif
+#ifdef _PLAN9_SOURCE
+/* FCR */
+#define	FPINEX	(3<<8)
+#define	FPOVFL	(1<<12)
+#define	FPUNFL	(1<<11)
+#define	FPZDIV	(1<<10)
+#define	FPRNR	(0<<4)
+#define	FPRZ	(1<<4)
+#define	FPRPINF	(3<<4)
+#define	FPRNINF	(2<<4)
+#define	FPRMASK	(3<<4)
+#define	FPPEXT	(0<<6)
+#define	FPPSGL	(1<<6)
+#define	FPPDBL	(2<<6)
+#define	FPPMASK	(3<<6)
+/* FSR */
+#define	FPAINEX	FPINEX
+#define	FPAOVFL	FPOVFL
+#define	FPAUNFL	FPUNFL
+#define	FPAZDIV	FPZDIV
+#endif
+#endif /* __FLOAT */

+ 65 - 0
68020/include/ape/math.h

@@ -0,0 +1,65 @@
+#ifndef __MATH
+#define __MATH
+#pragma lib "/$M/lib/ape/libap.a"
+
+/* a HUGE_VAL appropriate for IEEE double-precision */
+/* the correct value, 1.797693134862316e+308, causes a ken overflow */
+#define HUGE_VAL 1.79769313486231e+308
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern double acos(double);
+extern double asin(double);
+extern double atan(double);
+extern double atan2(double, double);
+extern double cos(double);
+extern double sin(double);
+extern double tan(double);
+extern double cosh(double);
+extern double sinh(double);
+extern double tanh(double);
+extern double exp(double);
+extern double frexp(double, int *);
+extern double ldexp(double, int);
+extern double log(double);
+extern double log10(double);
+extern double modf(double, double *);
+extern double pow(double, double);
+extern double sqrt(double);
+extern double ceil(double);
+extern double fabs(double);
+extern double floor(double);
+extern double fmod(double, double);
+extern double NaN(void);
+extern int isNaN(double);
+extern double Inf(int);
+extern int isInf(double, int);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#ifdef _RESEARCH_SOURCE
+/* does >> treat left operand as unsigned ? */
+#define Unsigned_Shifts 1
+#define	M_E		2.7182818284590452354	/* e */
+#define	M_LOG2E		1.4426950408889634074	/* log 2e */
+#define	M_LOG10E	0.43429448190325182765	/* log 10e */
+#define	M_LN2		0.69314718055994530942	/* log e2 */
+#define	M_LN10		2.30258509299404568402	/* log e10 */
+#define	M_PI		3.14159265358979323846	/* pi */
+#define	M_PI_2		1.57079632679489661923	/* pi/2 */
+#define	M_PI_4		0.78539816339744830962	/* pi/4 */
+#define	M_1_PI		0.31830988618379067154	/* 1/pi */
+#define	M_2_PI		0.63661977236758134308	/* 2/pi */
+#define	M_2_SQRTPI	1.12837916709551257390	/* 2/sqrt(pi) */
+#define	M_SQRT2		1.41421356237309504880	/* sqrt(2) */
+#define	M_SQRT1_2	0.70710678118654752440	/* 1/sqrt(2) */
+
+
+extern double hypot(double, double);
+#endif
+#endif /* __MATH */

+ 11 - 0
68020/include/ape/stdarg.h

@@ -0,0 +1,11 @@
+#ifndef __STDARG
+#define __STDARG
+
+typedef char *va_list;
+
+#define va_start(list, start) list = (char *)(&(start)+1)
+#define va_end(list)
+#define va_arg(list, mode) (sizeof(mode)==1 ? ((mode *) (list += 4))[-4] : \
+sizeof(mode)==2 ? ((mode *) (list += 4))[-2] : ((mode *) (list += sizeof(mode)))[-1])
+
+#endif /* __STDARG */

+ 36 - 0
68020/include/ape/ureg.h

@@ -0,0 +1,36 @@
+#ifndef __UREG_H
+#define __UREG_H
+#if !defined(_PLAN9_SOURCE)
+    This header file is an extension to ANSI/POSIX
+#endif
+
+struct Ureg
+{
+	unsigned long	r0;
+	unsigned long	r1;
+	unsigned long	r2;
+	unsigned long	r3;
+	unsigned long	r4;
+	unsigned long	r5;
+	unsigned long	r6;
+	unsigned long	r7;
+	unsigned long	a0;
+	unsigned long	a1;
+	unsigned long	a2;
+	unsigned long	a3;
+	unsigned long	a4;
+	unsigned long	a5;
+	unsigned long	a6;
+	unsigned long	sp;
+	unsigned long	usp;
+	unsigned long	magic;		/* for db to find bottom of ureg */
+	unsigned short	sr;
+	unsigned long	pc;
+	unsigned short	vo;
+#ifndef	UREGVARSZ
+#define	UREGVARSZ 23		/* for 68040; 15 is enough on 68020 */
+#endif
+	unsigned char	microstate[UREGVARSZ];	/* variable-sized portion */
+};
+
+#endif

+ 14 - 0
68020/include/dac.h

@@ -0,0 +1,14 @@
+/*
+ * Inmos G17x d/a converter
+ */
+
+typedef
+struct	G170
+{
+	uchar	waddr;
+	uchar	value;
+	uchar	mask;
+	uchar	raddr;
+} G170;
+
+#define DAC	((G170*)0xc0100000)

+ 3 - 0
68020/include/lbp.h

@@ -0,0 +1,3 @@
+typedef struct Lbpconf {
+	int nhblank, nvblank, xmax, ymax, res;
+} Lbpconf;

+ 60 - 0
68020/include/u.h

@@ -0,0 +1,60 @@
+#define nil		((void*)0)
+typedef	unsigned short	ushort;
+typedef	unsigned char	uchar;
+typedef	unsigned long	ulong;
+typedef	unsigned int	uint;
+typedef	signed char	schar;
+typedef	long long	vlong;
+typedef	unsigned long long uvlong;
+typedef	ushort		Rune;
+typedef 	union FPdbleword FPdbleword;
+typedef long	jmp_buf[2];
+#define	JMPBUFSP	0
+#define	JMPBUFPC	1
+#define	JMPBUFDPC	0
+typedef unsigned int	mpdigit;	/* for /sys/include/mp.h */
+typedef unsigned int	u32int;
+
+/* FCR */
+#define	FPINEX	(3<<8)
+#define	FPOVFL	(1<<12)
+#define	FPUNFL	(1<<11)
+#define	FPZDIV	(1<<10)
+#define	FPRNR	(0<<4)
+#define	FPRZ	(1<<4)
+#define	FPINVAL	(3<<13)
+#define	FPRPINF	(3<<4)
+#define	FPRNINF	(2<<4)
+#define	FPRMASK	(3<<4)
+#define	FPPEXT	(0<<6)
+#define	FPPSGL	(1<<6)
+#define	FPPDBL	(2<<6)
+#define	FPPMASK	(3<<6)
+/* FSR */
+#define	FPAINEX	FPINEX
+#define	FPAOVFL	FPOVFL
+#define	FPAUNFL	FPUNFL
+#define	FPAZDIV	FPZDIV
+#define	FPAINVAL	FPINVAL
+union FPdbleword
+{
+	double	x;
+	struct {	/* big endian */
+		ulong hi;
+		ulong lo;
+	};
+};
+
+typedef	char*	va_list;
+#define va_start(list, start) list =\
+	(sizeof(start) < 4?\
+		(char*)((int*)&(start)+1):\
+		(char*)(&(start)+1))
+#define va_end(list)\
+	USED(list)
+#define va_arg(list, mode)\
+	((sizeof(mode) == 1)?\
+		((mode*)(list += 4))[-1]:\
+	(sizeof(mode) == 2)?\
+		((mode*)(list += 4))[-1]:\
+		((mode*)(list += sizeof(mode)))[-1])

+ 28 - 0
68020/include/ureg.h

@@ -0,0 +1,28 @@
+struct Ureg
+{
+	ulong	r0;
+	ulong	r1;
+	ulong	r2;
+	ulong	r3;
+	ulong	r4;
+	ulong	r5;
+	ulong	r6;
+	ulong	r7;
+	ulong	a0;
+	ulong	a1;
+	ulong	a2;
+	ulong	a3;
+	ulong	a4;
+	ulong	a5;
+	ulong	a6;
+	ulong	sp;
+	ulong	usp;
+	ulong	magic;		/* for db to find bottom of ureg */
+	ushort	sr;
+	ulong	pc;
+	ushort	vo;
+#ifndef	UREGVARSZ
+#define	UREGVARSZ 23		/* for 68040; 15 is enough on 68020 */
+#endif
+	uchar	microstate[UREGVARSZ];	/* variable-sized portion */
+};

+ 7 - 0
68020/mkfile

@@ -0,0 +1,7 @@
+</sys/src/mkfile.proto
+
+CC=2c
+LD=2l
+O=2
+RL=rl
+AS=2a

+ 38 - 0
960/include/u.h

@@ -0,0 +1,38 @@
+#define nil		((void*)0)
+#define	float	long
+#define	double	long
+typedef	unsigned short	ushort;
+typedef	unsigned char	uchar;
+typedef	unsigned long	ulong;
+typedef	unsigned int	uint;
+typedef	signed char	schar;
+typedef	long		vlong;
+typedef	unsigned long	uvlong;
+typedef	ushort		Rune;
+typedef 	union FPdbleword FPdbleword;
+typedef long	jmp_buf[2];
+typedef unsigned int	mpdigit;	/* for /sys/include/mp.h */
+typedef unsigned int	u32int;
+
+union FPdbleword
+{
+	double	x;
+	struct {	/* little endian */
+		ulong lo;
+		ulong hi;
+	};
+};
+
+typedef	char*	va_list;
+#define va_start(list, start) list =\
+	(sizeof(start) < 4?\
+		(char*)((int*)&(start)+1):\
+		(char*)(&(start)+1))
+#define va_end(list)\
+	USED(list)
+#define va_arg(list, mode)\
+	((sizeof(mode) == 1)?\
+		((mode*)(list += 4))[-4]:\
+	(sizeof(mode) == 2)?\
+		((mode*)(list += 4))[-2]:\
+		((mode*)(list += sizeof(mode)))[-1])

+ 7 - 0
960/mkfile

@@ -0,0 +1,7 @@
+</sys/src/mkfile.proto
+
+CC=6c
+LD=6l
+O=6
+RL=rl
+AS=6a

+ 658 - 0
LICENSE

@@ -0,0 +1,658 @@
+
+LUCENT TECHNOLOGIES INC.
+PLAN 9 OPEN SOURCE LICENSE AGREEMENT
+
+PLEASE READ THIS AGREEMENT (INCLUDING THE EXHIBITS) CAREFULLY BEFORE
+PROCEEDING.  BY CLICKING ON THE "ACCEPT" BUTTON, OR BY DOWNLOADING,
+INSTALLING, USING, COPYING, MODIFYING OR DISTRIBUTING THE SOFTWARE OR
+DERIVATIVE WORKS THEREOF, YOU ARE CONSENTING TO BE BOUND BY THIS
+AGREEMENT.  IF YOU DO NOT AGREE TO ALL OF THE TERMS OF THIS AGREEMENT,
+CLICK ON THE "DO NOT ACCEPT" BUTTON AND THE INSTALLATION/DOWNLOAD
+PROCESS WILL NOT CONTINUE.
+
+1.	DEFINITIONS
+
+1.1 "Agreement" means this Lucent Technologies Inc. Plan 9 Open Source
+License Agreement (including Exhibits).
+1.2 "Contributor(s)" means any individual or legal entity that creates
+or contributes to a Modification of the Original Software.
+1.3 "Licensee" means an individual or a legal entity entering into and
+exercising rights under this Agreement.  For the purposes hereunder,
+Licensee includes any entity that controls, is controlled by, or is
+under common control with Licensee.  For purposes of this definition,
+"control" means (i) the power, direct or indirect, to cause the
+direction or management of such entity, whether by contract or
+otherwise; or (ii) ownership of fifty percent (50%) or more of the
+controlling shares or beneficial ownership of such entity.  Licensee
+is also referred to herein as "You" with "Your" as the possessive.
+1.4 "Licensed Software" means the Original Software, Modifications, or
+any combination of the Original Software and Modifications.
+1.5 "Lucent" means Lucent Technologies Inc., a Delaware corporation
+having an office at 600 Mountain Ave., Murray Hill, NJ 07974, its
+related companies and/or affiliates.
+1.6 "Modification(s)" means any addition, deletion, change, or
+improvement to the Original Software or prior Modifications thereto.
+Modifications do not include additions to the Original Software or
+prior Modifications which (i) are separate modules of software which
+may be distributed in conjunction with Licensed Software; or (ii) are
+not derivative works of the Licensed Software itself.
+1.7 "Object Code" means machine executable software code.
+1.8 "Original Contributor" means Lucent and its Licensors, collectively.
+1.9 "Original Software" means the Plan 9 Software, in both Source Code
+form and Object Code form, and any associated documentation, as
+furnished under this Agreement.
+1.10 "Plan 9 Software" means a network operating system designed for
+research into distributed services, applications and software
+development.
+1.11 "Plan 9 Trademark" means the trademark PLAN 9 (for which Lucent
+has acquired common law rights and for which Lucent owns U.S.
+Trademark Registration Number 2,065,577).
+1.12 "Recipient" means any individual or legal entity receiving the
+Licensed Software under this Agreement, including all Contributors, or
+receiving the Licensed Software under another license agreement as
+authorized herein.
+1.13 "Source Code" means human readable software code.
+2.0	GRANT OF RIGHTS
+2.1 Subject to the terms of this Agreement and to third party
+intellectual property claims, Lucent grants to Licensee, a
+royalty-free, nonexclusive, non-transferable, worldwide license to
+use, reproduce, modify, execute, display, perform, distribute and
+sublicense, the Original Software (with or without Modifications) in
+Source Code form and/or Object Code form for commercial and/or
+non-commercial purposes.  This grant includes a nonexclusive and
+non-transferable license under any patents which Lucent has a right to
+license and which, but for this license, are unavoidably and
+necessarily infringed by the execution of the inherent functionality
+of the Original Software in the form furnished under this Agreement.
+Nothing in this Agreement shall be construed as conferring in any way
+(by implication, estoppel or otherwise) any license or right under any
+existing or future patent claim which is directed to a combination of
+the functionality of the Original Software with the functionality of
+any other software programs, or a combination of hardware systems
+other than the combination of the Original Software and the hardware
+or firmware into which the Original Software is loaded.  Distribution
+of Licensed Software to third parties pursuant to this grant shall be
+subject to the same terms and conditions as set forth in this
+Agreement, and may, at Your option, include a reasonable charge for
+the cost of any media.  You may also, at Your option, charge for any
+other software, product or service that includes or incorporates the
+Original Software as a part thereof.
+
+2.2 No right is granted to Licensee to create derivative works of or
+to redistribute (other than with the Original Software or a derivative
+thereof) the screen imprinter fonts identified in subdirectory
+/lib/font/bit/lucida and printer fonts (Lucida Sans Unicode, Lucida
+Sans Italic, Lucida Sans Demibold, Lucida Typewriter, Lucida Sans
+Typewriter83), identified in subdirectory /sys/lib/postscript/font.
+
+2.3 Exhibit A contains additional terms and conditions relating to the
+printer fonts identified in subdirectory /sys/lib/ghostscript/font.
+In the case of any conflict between the provisions of the body of this
+Agreement and Exhibit A regarding such printer fonts, the provisions
+of Exhibit A shall control.
+
+2.4 The Original Software licensed herein contains material copyrights
+by the Original Contributor, including but not limited to Lucent, B&H
+Inc., and Y&Y Inc. No rights are granted with respect to Original
+Software except as expressly provided herein.
+
+2.5 Lucent grants to Licensee a nonexclusive, royalty free, worldwide
+license to use the Plan 9 Trademark solely in connection with the Plan
+9 operating system source code and documentation.  Such use by
+Licensee of the Plan 9 Trademark shall be in accordance with the
+following quality standards and controls:
+
+* Any use of the Plan 9 Trademark must be made under the terms of this
+Agreement;
+
+* The Plan 9 Trademark may not be combined with any other mark or logo
+to form a composite mark or logo or suggest that the Parties are part
+of one company;
+
+* The Plan 9 Trademark font must be smaller than the font used for
+Licensee's own trademarks and/or logos.
+
+Upon Lucent's written request and at Licensee's expense, Licensee will
+provide Lucent with a representative sample of Licensee's promotional
+materials bearing the Plan 9 Trademark.  If, for any reason, Lucent
+determines that the quality standards or controls applied by Licensee
+to the Plan 9 system source code and documentation fall below those
+that are consistent with Lucent's standards, upon written notice of
+the deficiency to Licensee, Lucent may, at its sole option and
+discretion, terminate Licensee's right to use the Plan 9 Trademark
+upon written notice to Licensee.
+
+Licensee acknowledges that Lucent is the owner of the Plan 9 Trademark
+and all goodwill attached thereto.  This Agreement does not give
+Licensee any interest in the Plan 9 Trademark except the right to use
+the mark in accordance with the provisions of this Agreement.
+Licensee agrees not to attempt to register the Plan 9 Trademark nor to
+adopt, attempt to register or register anywhere in the world a mark
+the same as or confusingly similar to the Plan 9 Trademark.
+
+
+3.0 	DISTRIBUTION OBLIGATIONS
+
+3.1 Modifications which You create or to which You contribute are
+governed by the terms of this Agreement and must be made available
+under the terms of this Agreement in at least the same form as the Source
+Code version of Original Software furnished hereunder.  Any
+distribution by You of the Source Code version of Licensed Software
+must be made under the terms of this Agreement or any future version
+of this Agreement under Section 11.0, and You must include a copy of
+this Agreement with each and every copy of such Source Code version of
+Licensed Software which You distribute.  You may not offer or impose
+any terms on any such Source Code version of Licensed Software that
+alters or restricts the terms of the applicable version of this
+Agreement or the Recipients' rights and obligations hereunder.
+3.2 You must cause all Licensed Software to which You contribute, i.e.
+Your Modifications, to contain a clear identification, e.g., a
+separate file, documenting the changes made by You and identifying You
+as the Contributor that reasonably allows subsequent Recipients to
+identify the originator of the Modification.  To the extent You create
+at least one Modification, You may add Your name as a Contributor to
+the requisite notice described in Section 3.3.
+3.3 With respect to Your distribution of Licensed Software (or any
+portion thereof), You must include the following information in a
+conspicuous location governing such distribution (e.g., a separate
+file) and on all copies of any Source Code version of Licensed
+Software You distribute:
+	"The contents herein includes software initially developed by
+	Lucent Technologies Inc. and others, and is subject to the terms
+	of the Lucent Technologies Inc. Plan 9 Open Source License
+	Agreement.  A copy of the Plan 9 Open Source License Agreement is
+	available at: http://plan9.bell-labs.com/plan9dist/download.html
+	or by contacting Lucent Technologies at http: //www.lucent.com.
+	All software distributed under such Agreement is distributed on
+	an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+	implied.  See the Lucent Technologies Inc. Plan 9 Open Source
+	License Agreement for the specific language governing all rights,
+	obligations and limitations under such Agreement.  Portions of
+	the software developed by Lucent Technologies Inc. and others are
+	Copyright (c) 2002.  All rights reserved.
+	Contributor(s):___________________________"
+3.4 You may distribute Licensed Software in Object Code form using
+this Agreement, or under a license of Your choice provided that You
+are in compliance with this Agreement and Your license: (a) complies
+with the terms and conditions of this Agreement; (b) does not limit or
+alter the Recipient's rights and obligations in the Source Code
+version of the Licensed Software set forth in this Agreement; (c)
+states that the Source Code version of the Licensed Software is
+available from You, and describes how it may be obtained by
+Recipient; (d) effectively disclaims on behalf of Original Contributor
+and all Contributors all warranties and conditions, express or
+implied, including warranties or conditions of title or
+non-infringement, and implied warranties or conditions of
+merchantability and fitness for a particular purpose; (e) effectively
+excludes on behalf of Original Contributor and all Contributors all
+liability for damages, including direct, indirect, special,
+incidental, and consequential damages; and (f) clearly states that any
+terms which differ from this Agreement are offered by You alone, not
+by Original Contributor or any other Contributor.  You hereby agree to
+indemnify Original Contributor or any other Contributor for any
+liability incurred by Original Contributor or any other Contributor as
+result of any such differing terms You offer in Your license.
+3.5 You may not use the names "Lucent Technologies", "Bell Labs" or
+any other name associated with Lucent or any Lucent trademark for any
+purposes other than as specifically provided in this Agreement.
+3.6 You must include all of the original copyright, labels or other
+notices on the Licensed Software on any copies of the Licensed
+Software which You make; and include with the distribution of any
+Modifications You create a copy (or an offer to provide such a copy at
+no charge) of the Licensed Software, on the same terms as set forth in
+this Agreement.
+3.7 While this Agreement contemplates the commercial use and
+distribution of Licensed Software, commercial distributors of software
+may, for a variety of reasons, accept certain responsibilities with
+respect to customers, licensees, business partners and the like.  As
+such, if You or any Contributor include Licensed Software in a
+commercial offering ("Commercial Contributor"), such Commercial
+Contributor agrees to defend and indemnify Original Contributor and
+all other Contributors (collectively "Indemnified Contributors")
+against any liability, losses, damages and costs arising from claims,
+lawsuits and other legal actions brought by any third party against
+the Indemnified Contributors to the extent caused by the acts or
+omissions of such Commercial Contributor in connection with its use or
+distribution of Licensed Software in a commercial offering of any
+kind.
+
+4.0 	MODIFICATIONS
+You agree to provide the Original Contributor, at its request, with a
+copy of the complete Source Code version, Object Code version and
+related documentation for Modifications created or contributed to by
+You if distributed in any form, e.g., binary or source.  Original
+Contributor and/or other Contributors shall have unrestricted,
+nonexclusive, worldwide, perpetual, royalty-free rights, to use,
+reproduce, modify, display, perform, sublicense and distribute such
+Modifications, and to grant third parties the right to do so,
+including without limitation as a part of or with the Licensed
+Software; and Original Contributor and/or other Contributors shall
+have the right to license or to otherwise transfer to third parties
+such Modifications without notice, obligation or recourse to You. You
+grant to Original Contributor, Contributors and their respective
+licensees all rights and licenses (including patents) as are necessary
+to incorporate the Modifications created or contributed and so
+distributed by You into the Licensed Software and to use, distribute
+or otherwise exploit such Licensed Software without payment or
+accounting to You.
+
+5.0 TITLE
+Title, ownership rights, and intellectual property rights in the
+Original Software and the Plan 9 Trademark shall remain in the
+Original Contributor.  Original Contributor and/or the other
+Contributors reserve all rights not expressly granted to You, and no
+other licenses are granted or implied.  The Licensed Software is
+protected by copyright laws and treaties.
+
+6.0  	TERMINATION
+6.1 The licenses and rights granted under this Agreement shall
+terminate automatically if (i) You fail to comply with all of the
+terms and conditions herein; or (ii) You initiate or participate in
+any intellectual property action against Original Contributor.
+
+6.2 The rights and obligations of the parties hereto which by their
+nature would continue beyond termination of this Agreement shall
+survive and continue after any such termination of this Agreement.
+
+6.3 Upon termination for any reason, You must destroy all copies of
+the Licensed Software in Your possession.  All sublicenses of Licensed
+Software which were validly granted by You to third parties under this
+Agreement shall survive such termination.
+
+7.0  DISCLAIMER OF WARRANTY
+
+YOU UNDERSTAND AND ACKNOWLEDGE THAT, TO THE FULLEST EXTENT PERMITTED
+BY LAW, THE LICENSED SOFTWARE IS LICENSED UNDER THIS AGREEMENT FREE OF
+CHARGE ON AN "AS IS" BASIS WITH ALL FAULTS, LATENT AND PATENT AND
+WITHOUT ANY WARRANTY OF ANY TYPE.  ORIGINAL CONTRIBUTOR AND THE OTHER
+CONTRIBUTORS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESSED OR
+IMPLIED.  BY WAY OF EXAMPLE, BUT NOT OF LIMITATION, ORIGINAL
+CONTRIBUTOR AND THE OTHER CONTRIBUTORS MAKE NO REPRESENTATIONS OF
+MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE OR THAT THE USE OF
+THE LICENSED SOFTWARE WILL NOT INFRINGE ANY PATENT OR OTHER
+INTELLECTUAL PROPERTY RIGHT OF ANY THIRD PARTY AND IT SHALL BE THE
+SOLE RESPONSIBILITY OF YOU TO MAKE SUCH DETERMINATION AS IS NECESSARY
+WITH RESPECT TO THE ACQUISITION OF LICENSES UNDER PATENTS OR OTHER
+INTELLECTUAL PROPERTY RIGHTS OF THIRD PARTIES.  ORIGINAL CONTRIBUTOR
+AND THE OTHER CONTRIBUTORS DO NOT WARRANT THAT THE FUNCTIONS OF THE
+LICENSED SOFTWARE WILL MEET YOUR REQUIREMENTS OR THAT LICENSED
+SOFTWARE OPERATION WILL BE ERROR-FREE OR UNINTERRUPTED.  YOU ASSUME
+THE RISK OF ANY AND ALL DAMAGE OR LOSS FROM USE, OR INABILITY TO USE,
+THE LICENSED SOFTWARE.  ORIGINAL CONTRIBUTOR AND THE OTHER
+CONTRIBUTORS BEAR NO RESPONSIBILITY FOR CORRECTING THE LICENSED
+SOFTWARE, SUPPLYING ASSISTANCE FOR FIXING, OR FOR COMMUNICATING KNOWN
+ERRORS TO YOU PERTAINING TO THE LICENSED SOFTWARE FURNISHED HEREUNDER.
+
+ORIGINAL CONTRIBUTOR AND THE OTHER CONTRIBUTORS SHALL NOT BE HELD TO
+ANY LIABILITY WITH RESPECT TO ANY PATENT INFRINGEMENT OR ANY OTHER
+CLAIM MADE BY YOU OR ANY THIRD PARTY ON ACCOUNT OF, OR ARISING FROM
+THE USE OF, THE LICENSED SOFTWARE PROVIDED HEREUNDER.
+
+SOME STATES DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO THE
+ABOVE EXCLUSION MAY NOT APPLY TO YOU. YOU MAY ALSO HAVE OTHER RIGHTS
+THAT VARY FROM JURISDICTION TO JURISDICTION.
+
+8.0  	LIMITATION OF LIABILITY
+UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, TORT, CONTRACT, OR
+OTHERWISE, SHALL ORIGINAL CONTRIBUTOR AND/OR THE OTHER CONTRIBUTORS BE
+LIABLE TO YOU OR ANY OTHER THIRD PARTY FOR DAMAGES OF ANY KIND
+INCLUDING, BUT NOT LIMITED TO, ANY DIRECT, INDIRECT, SPECIAL,
+INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER WHATSOEVER
+INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, WORK
+STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER
+COMMERCIAL DAMAGES OR LOSSES, EVEN IF THE ORIGINAL CONTRIBUTOR AND/OR
+ANY OTHER CONTRIBUTORS SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF
+SUCH DAMAGES, OR FOR ANY CLAIM BY ANY OTHER PARTY.  FURTHERMORE, SOME
+JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL
+OR CONSEQUENTIAL DAMAGES, SO THIS LIMITATION AND EXCLUSION MAY NOT
+APPLY TO YOU. TO THE EXTENT THAT ANY EXCLUSION OF DAMAGES ABOVE IS NOT
+VALID, YOU AGREE THAT IN NO EVENT WILL ORIGINAL CONTRIBUTOR'S AND ALL
+OTHER CONTRIBUTORS' TOTAL LIABILITY UNDER OR RELATED TO THIS AGREEMENT
+EXCEED ONE THOUSAND DOLLARS ($1000.00 US).
+
+9.0  	EXPORT CONTROL
+You acknowledge that the Licensed Software hereunder is "unrestricted
+encryption source code" as the term is defined under the United States
+Export Administration Regulations and is subject to export control
+under such laws and regulations.  You agree that, if you export or
+re-export the Licensed Software or any modifications to it, You are
+responsible for compliance with the United States Export
+Administration Regulations and hereby indemnify the Original
+Contributor and all other Contributors for any liability incurred as a
+result.
+
+10.0  	U.S. GOVERNMENT RIGHTS
+
+You may only acquire the Licensed Software on behalf of, or for
+delivery to, any part of the United States Government, if the Licensed
+Software is treated as commercial computer software and licensed to
+the Government under the terms and conditions of this Agreement,
+pursuant to the policies stated in 48 C.F.R.  Section 12.212 (October
+1995) or 48 C.F.R.  Section 227.7202 (June 1995), as applicable.
+
+11.0 	LICENSE VERSIONS
+
+LUCENT, at its sole discretion, may from time to time publish a
+revised and/or new version of this Agreement (each such revised or new
+version shall carry a distinguishing version number) which shall
+govern all copies of Licensed Software downloaded after the posting of
+such revised or new version of this Agreement.
+
+12.0  	MISCELLANEOUS
+This Agreement sets forth the entire agreement and understanding
+between the parties as to the subject matter hereof and merges all
+prior discussions between them.  This Agreement shall be governed by
+the laws of the State of New York, USA, excluding its conflict of law
+provisions.  The application of the United Nations Convention of
+Contracts for the International Sale of Goods is expressly excluded.
+YOUR DOWNLOAD, INSTALLATION AND USE, MODIFICATION OR DISTRIBUTION OF
+THE LICENSED SOFTWARE IS EXPRESSLY MADE CONDITIONAL ON YOUR ASSENT TO
+THE TERMS SET FORTH HEREIN.  You further agree and acknowledge that by
+clicking on the "ACCEPT" button below, You shall have manifested
+acceptance to enter into this Agreement and shall be deemed to have
+manually signed and executed this Agreement making this an enforceable
+Agreement between the parties.  If any provision of this Agreement is
+held to be unenforceable, such provision shall be reformed only to the
+extent necessary to make it enforceable.
+ 
+
+
+ 
+
+EXHIBIT A - GNU GENERAL PUBLIC LICENSE
+
+
+GNU GENERAL PUBLIC LICENSE
+
+Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave,
+Cambridge, MA 02139, USA. Everyone is permitted to copy and distribute
+verbatim copies of this license document, but changing it is not
+allowed.
+
+Preamble
+
+The licenses for most software are designed to take away your freedom
+to share and change it.  By contrast, the GNU General Public License
+is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if
+you distribute copies of the software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on,
+we want its recipients to know that what they have is not the
+original, so that any problems introduced by others will not reflect
+on the original authors' reputations.
+
+Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at
+all.
+
+The precise terms and conditions for copying, distribution and
+modification follow.
+
+GNU GENERAL PUBLIC LICENSE
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+0.  This License applies to any program or other work which contains a
+notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the Program
+(independent of having been made by running the Program).  Whether
+that is true depends on what the Program does.
+
+1.  You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a
+fee.
+
+2.  You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+a) You must cause the modified files to carry prominent notices
+stating that you changed the files and the date of any change.
+
+b) You must cause any work that you distribute or publish, that in
+whole or in part contains or is derived from the Program or any part
+thereof, to be licensed as a whole at no charge to all third parties
+under the terms of this License.
+
+c) If the modified program normally reads commands interactively when
+run, you must cause it, when started running for such interactive use
+in the most ordinary way, to print or display an announcement
+including an appropriate copyright notice and a notice that there is
+no warranty (or else, saying that you provide a warranty) and that
+users may redistribute the program under these conditions, and telling
+the user how to view a copy of this License.  (Exception: if the
+Program itself is interactive but does not normally print such an
+announcement, your work based on the Program is not required to print
+an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+3.  You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+a) Accompany it with the complete corresponding machine-readable
+source code, which must be distributed under the terms of Sections 1
+and 2 above on a medium customarily used for software interchange; or,
+
+b) Accompany it with a written offer, valid for at least three years,
+to give any third party, for a charge no more than your cost of
+physically performing source distribution, a complete machine-readable
+copy of the corresponding source code, to be distributed under the
+terms of Sections 1 and 2 above on a medium customarily used for
+software interchange; or,
+
+c) Accompany it with the information you received as to the offer to
+distribute corresponding source code.  (This alternative is allowed
+only for noncommercial distribution and only if you received the
+program in object code or executable form with such an offer, in
+accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+4.  You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+5.  You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+6.  Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+7.  If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+8.  If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+9.  The Free Software Foundation may publish revised and/or new
+versions of the General Public License from time to time.  Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Program does not specify a
+version number of this License, you may choose any version ever
+published by the Free Software Foundation.
+
+10.  If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the
+author to ask for permission.  For software which is copyrighted by
+the Free Software Foundation, write to the Free Software Foundation;
+we sometimes make exceptions for this.  Our decision will be guided by
+the two goals of preserving the free status of all derivatives of our
+free software and of promoting the sharing and reuse of software
+generally.
+
+NO WARRANTY
+
+11.  BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+12.  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+END OF TERMS AND CONDITIONS
+
+
+
+YOU ACKNOWLEDGE THAT YOU HAVE READ THIS AGREEMENT (INCLUDING THE
+EXHIBITS) AND UNDERSTAND IT, AND THAT BY CLICKING ON THE "ACCEPT"
+BUTTON BELOW AND INSTALLING/DOWNLOADING THE SOFTWARE YOU ACCEPT AND
+AGREE TO BE BOUND BY THE TERMS AND CONDITIONS OF THIS AGREEMENT.
+Plan 9 Open Source License - Version 1.4 - 09/10/02 1

+ 2 - 0
NOTICE

@@ -0,0 +1,2 @@
+Copyright © 2002 Lucent Technologies Inc.
+All Rights Reserved

+ 6 - 0
acme/acid/Acid

@@ -0,0 +1,6 @@
+#!/bin/rc
+if(~ $#* 0){
+	echo usage: Acid pid >[2=1]
+	exit usage
+}
+win acid -l acme $*

+ 3 - 0
acme/acid/guide

@@ -0,0 +1,3 @@
+broke|rc    kill program|rc
+Acid pid
+Acid -l thread -l acidfile pid

+ 30 - 0
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

+ 7 - 0
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]

+ 3 - 0
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.

+ 24 - 0
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

+ 3 - 0
acme/bin/agrep

@@ -0,0 +1,3 @@
+#!/bin/rc
+
+exec grep -n $* /dev/null

+ 12 - 0
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

+ 43 - 0
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

+ 4 - 0
acme/bin/guide

@@ -0,0 +1,4 @@
+win
+aspell file
+adiff file1 file2
+adict -d oed

+ 3 - 0
acme/bin/ind

@@ -0,0 +1,3 @@
+#!/bin/rc
+
+sed 's/^/	/' $*

+ 10 - 0
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

+ 3 - 0
acme/bin/quote

@@ -0,0 +1,3 @@
+#!/bin/rc
+
+sed 's/^/> /' $*

+ 33 - 0
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

+ 243 - 0
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.

+ 171 - 0
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*);
+
+

+ 347 - 0
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;
+		}
+	}
+}

+ 206 - 0
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>

+ 170 - 0
acme/bin/source/acd/cddb.c

@@ -0,0 +1,170 @@
+#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 int
+cddbfilltoc(Toc *t)
+{
+	int fd;
+	int i;
+	char *p, *q;
+	Biobuf bin;
+	Msf *m;
+	char *f[10];
+	int nf;
+	char *id, *categ;
+
+	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);
+	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)
+			t->title = estrdup(p+7);
+		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++;
+
+				t->track[i].title = estrdup(p);
+			}
+		} 
+	} 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);
+}

+ 894 - 0
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>

+ 159 - 0
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.

+ 220 - 0
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>

+ 135 - 0
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);
+}

+ 22 - 0
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

+ 303 - 0
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);
+	}
+}

+ 32 - 0
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.

+ 220 - 0
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.

+ 59 - 0
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);
+
+}

+ 89 - 0
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");
+}

+ 320 - 0
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;
+}

+ 584 - 0
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;
+		}
+	}
+}

+ 10 - 0
acme/bin/source/adict/adict.h

@@ -0,0 +1,10 @@
+enum
+{
+	Matchwin,
+	Entrywin,
+	Dictwin
+};
+
+#define MAXTAG	20
+#define MAXMATCH 100
+#define BUFSIZE	4096

+ 26 - 0
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.

+ 11 - 0
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

+ 315 - 0
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;
+}

+ 59 - 0
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);

+ 27 - 0
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
+	}

+ 45 - 0
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);
+}

+ 123 - 0
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);
+}

+ 95 - 0
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;
+

+ 146 - 0
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,
+};
+
+void
+mountcons(void)
+{
+//chatty9p=1;
+	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);
+}

+ 654 - 0
acme/bin/source/win/main.c

@@ -0,0 +1,654 @@
+#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 =1;
+		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();
+	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 = r->oldreq->aux;
+					if(*l == nil)
+						eq = l;
+					closereq(r->oldreq);
+					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);
+	}
+	sendul(cpid, 0UL);
+	threadexits("can't exec");
+}
+
+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);
+	if(pid == 0){
+		error("can't exec %s: %r", argv[0]);
+		threadexitsall("can't exec");
+	}
+	sprint(buf, "/proc/%d/notepg", pid);
+	*notepg = open(buf, OWRITE);
+}

+ 24 - 0
acme/bin/source/win/mkfile

@@ -0,0 +1,24 @@
+</$objtype/mkfile
+
+TARG=win
+OFILES=\
+	fs.$O\
+	main.$O\
+	pipe.$O\
+	util.$O\
+	win.$O
+
+HFILES=dat.h
+
+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

+ 175 - 0
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("win: 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);
+}

+ 90 - 0
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");
+}

+ 264 - 0
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;
+}

+ 3 - 0
acme/bin/unind

@@ -0,0 +1,3 @@
+#!/bin/rc
+
+sed 's/^	//' $*

+ 9 - 0
acme/mail/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/mail/$objtype/Mail $*

+ 4 - 0
acme/mail/guide

@@ -0,0 +1,4 @@
+Mail stored
+plumb /mail/box/$user/names
+mail -'x' someaddress
+mkbox /mail/box/$user/new_box

+ 11 - 0
acme/mail/mkbox

@@ -0,0 +1,11 @@
+#!/bin/rc
+
+for(i){
+	if(! test -f $i){
+		if(cp /dev/null $i){
+			chmod 600 $i
+			chmod +al $i
+		}
+	}
+	if not echo $i already exists
+}

+ 0 - 0
acme/mail/old.iostats


+ 55 - 0
acme/mail/readme

@@ -0,0 +1,55 @@
+The Acme Mail program uses upas/fs to parse the mail box, and then
+presents a file-browser-like user interface to reading and sending
+messages.  The Mail window presents each numbered message like the
+contents of a directory presented one per line.  If a message has a
+Subject: line, that is shown indented on the following line.
+Multipart MIME-encoded messages are presented in the obvious
+hierarchical format.
+
+Mail uses upas/fs to access the mail box.  By default it reads "mbox",
+the standard user mail box.  If Mail is given an argument, it is
+passed to upas/fs as the name of the mail box (or upas/fs directory)
+to open.
+
+Although Mail works if the plumber is not running, it's designed to be
+run with plumbing enabled and many of its features work best if it is.
+
+The mailbox window has a few commands: Put writes back the mailbox;
+Mail creates a new window in which to compose a message; and Delmesg
+deletes messages by number.  The number may be given as argument or
+indicated by selecting the header line in the mailbox window.
+(Delmesg does not expand null selections, in the interest of safety.)
+
+Clicking the right button on a message number opens it; clicking on
+any of the subparts of a message opens that (and also opens the
+message itself).  Each message window has a few commands in the tag
+with obvious names: Reply, Delmsg, etc.  "Reply" replies to the single
+sender of the message, "Reply all" or "Replyall" replies to everyone
+in the From:, To:, and CC: lines.
+
+Message parts with recognized MIME types such as image/jpeg are sent
+to the plumber for further dispatch.  Acme Mail also listens to
+messages on the seemail and showmail plumbing ports, to report the
+arrival of new messages (highlighting the entry; right-click on the
+entry to open the message) and open them if you right-click on the
+face in the faces window.
+
+When composing a mail message or replying to a message, the first line
+of the text is a list of recipients of the message.  To:, and CC:, and BCC:
+lines are interpreted in the usual way. Two other header lines are
+special to Acme Mail:
+        Include: file places a copy of file in the message as an
+		inline MIME attachment.
+        Attach: file places a copy of file in the message as a regular
+		MIME attachment.
+
+Acme Mail uses these conventions when replying to messages,
+constructing headers for the default behavior.  You may edit these to
+change behavior.  Most important, when replying to a message Mail will
+always Include: the original message; delete that line if you don't
+want to include it.
+
+If the mailbox
+	/mail/box/$user/outgoing
+exists, Acme Mail will save your a copy of your outgoing messages
+there.  Attachments are described in the copy but not included.

+ 164 - 0
acme/mail/src/dat.h

@@ -0,0 +1,164 @@
+typedef struct Event Event;
+typedef struct Exec Exec;
+typedef struct Message Message;
+typedef struct Window Window;
+
+enum
+{
+	STACK		= 8192,
+	EVENTSIZE	= 256,
+	NEVENT		= 5,
+};
+
+struct Event
+{
+	int	c1;
+	int	c2;
+	int	q0;
+	int	q1;
+	int	flag;
+	int	nb;
+	int	nr;
+	char	b[EVENTSIZE*UTFmax+1];
+	Rune	r[EVENTSIZE+1];
+};
+
+struct Window
+{
+	/* file descriptors */
+	int		ctl;
+	int		event;
+	int		addr;
+	int		data;
+	Biobuf	*body;
+
+	/* event input */
+	char		buf[512];
+	char		*bufp;
+	int		nbuf;
+	Event	e[NEVENT];
+
+	int		id;
+	int		open;
+	Channel	*cevent;
+};
+
+struct Message
+{
+	Window	*w;
+	int		ctlfd;
+	char		*name;
+	char		*replyname;
+	uchar	opened;
+	uchar	dirty;
+	uchar	isreply;
+	uchar	deleted;
+	uchar	writebackdel;
+	uchar	tagposted;
+	uchar	recursed;
+	uchar	level;
+
+	/* header info */
+	char		*fromcolon;	/* from header file; all rest are from info file */
+	char		*from;
+	char		*to;
+	char		*cc;
+	char		*replyto;
+	char		*date;
+	char		*subject;
+	char		*type;
+	char		*disposition;
+	char		*filename;
+	char		*digest;
+
+	Message	*next;	/* next in this mailbox */
+	Message	*prev;	/* prev in this mailbox */
+	Message	*head;	/* first subpart */
+	Message	*tail;		/* last subpart */
+};
+
+enum
+{
+	NARGS		= 100,
+	NARGCHAR	= 8*1024,
+	EXECSTACK 	= STACK+(NARGS+1)*sizeof(char*)+NARGCHAR
+};
+
+struct Exec
+{
+	char		*prog;
+	char		**argv;
+	int		p[2];	/* p[1] is write to program; p[0] set to prog fd 0*/
+	int		q[2];	/* q[0] is read from program; q[1] set to prog fd 1 */
+	Channel	*sync;
+};
+
+extern	Window*	newwindow(void);
+extern	int		winopenfile(Window*, char*);
+extern	void		winopenbody(Window*, int);
+extern	void		winclosebody(Window*);
+extern	void		wintagwrite(Window*, char*, int);
+extern	void		winname(Window*, char*);
+extern	void		winwriteevent(Window*, Event*);
+extern	void		winread(Window*, uint, uint, char*);
+extern	int		windel(Window*, int);
+extern	void		wingetevent(Window*, Event*);
+extern	void		wineventproc(void*);
+extern	void		winwritebody(Window*, char*, int);
+extern	void		winclean(Window*);
+extern	int		winselect(Window*, char*, int);
+extern	char*	winselection(Window*);
+extern	int		winsetaddr(Window*, char*, int);
+extern	char*	winreadbody(Window*, int*);
+extern	void		windormant(Window*);
+extern	void		winsetdump(Window*, char*, char*);
+
+extern	void		readmbox(Message*, char*, char*);
+extern	void		rewritembox(Window*, Message*);
+
+extern	void		mkreply(Message*, char*, char*, Plumbattr*, char*);
+extern	void		delreply(Message*);
+
+extern	int		mesgadd(Message*, char*, Dir*, char*);
+extern	void		mesgmenu(Window*, Message*);
+extern	void		mesgmenunew(Window*, Message*);
+extern	int		mesgopen(Message*, char*, char*, Message*, int, char*);
+extern	void		mesgctl(void*);
+extern	void		mesgsend(Message*);
+extern	void		mesgdel(Message*, Message*);
+extern	void		mesgmenudel(Window*, Message*, Message*);
+extern	void		mesgmenumark(Window*, char*, char*);
+extern	void		mesgmenumarkdel(Window*, Message*, Message*, int);
+extern	Message*	mesglookup(Message*, char*, char*);
+extern	Message*	mesglookupfile(Message*, char*, char*);
+extern	void		mesgfreeparts(Message*);
+
+extern	char*	readfile(char*, char*, int*);
+extern	char*	readbody(char*, char*, int*);
+extern	void		ctlprint(int, char*, ...);
+extern	void*	emalloc(uint);
+extern	void*	erealloc(void*, uint);
+extern	char*	estrdup(char*);
+extern	char*	estrstrdup(char*, char*);
+extern	char*	egrow(char*, char*, char*);
+extern	char*	eappend(char*, char*, char*);
+extern	void		error(char*, ...);
+extern	int		tokenizec(char*, char**, int, char*);
+extern	void		execproc(void*);
+
+#pragma	varargck	argpos	error	1
+#pragma	varargck	argpos	ctlprint	2
+
+extern	Window	*wbox;
+extern	Message	mbox;
+extern	Message	replies;
+extern	char		*fsname;
+extern	int		plumbsendfd;
+extern	int		plumbseemailfd;
+extern	char		*home;
+extern	char		*outgoing;
+extern	char		*mailboxdir;
+extern	char		*user;
+extern	char		deleted[];
+extern	int		wctlfd;
+extern	int		shortmenu;

+ 75 - 0
acme/mail/src/html.c

@@ -0,0 +1,75 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <ctype.h>
+#include <plumb.h>
+#include "dat.h"
+
+
+char*
+formathtml(char *body, int *np)
+{
+	int i, j, p[2], q[2];
+	Exec *e;
+	char buf[1024];
+	Channel *sync;
+
+	e = emalloc(sizeof(struct Exec));
+	if(pipe(p) < 0 || pipe(q) < 0)
+		error("can't create pipe: %r");
+
+	e->p[0] = p[0];
+	e->p[1] = p[1];
+	e->q[0] = q[0];
+	e->q[1] = q[1];
+	e->argv = emalloc(3*sizeof(char*));
+	e->argv[0] = estrdup("htmlfmt");
+	e->argv[1] = estrdup("-cutf-8");
+	e->argv[2] = nil;
+	e->prog = "/bin/htmlfmt";
+	sync = chancreate(sizeof(int), 0);
+	e->sync = sync;
+	proccreate(execproc, e, EXECSTACK);
+	recvul(sync);
+	close(p[0]);
+	close(q[1]);
+
+	if((i=write(p[1], body, *np)) != *np){
+		fprint(2, "Mail: warning: htmlfmt failed: wrote %d of %d: %r\n", i, *np);
+		close(p[1]);
+		close(q[0]);
+		return body;
+	}
+	close(p[1]);
+
+	free(body);
+	body = nil;
+	i = 0;
+	for(;;){
+		j = read(q[0], buf, sizeof buf);
+		if(j <= 0)
+			break;
+		body = realloc(body, i+j+1);
+		if(body == nil)
+			error("realloc failed: %r");
+		memmove(body+i, buf, j);
+		i += j;
+		body[i] = '\0';
+	}
+	close(q[0]);
+
+	*np = i;
+	return body;
+}
+
+char*
+readbody(char *type, char *dir, int *np)
+{
+	char *body;
+	
+	body = readfile(dir, "body", np);
+	if(strcmp(type, "text/html") == 0)
+		return formathtml(body, np);
+	return body;
+}

+ 532 - 0
acme/mail/src/mail.c

@@ -0,0 +1,532 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <plumb.h>
+#include <ctype.h>
+#include "dat.h"
+
+char	*maildir = "/mail/fs/";			/* mountpoint of mail file system */
+char	*mailtermdir = "/mnt/term/mail/fs/";	/* alternate mountpoint */
+char *mboxname = "mbox";			/* mailboxdir/mboxname is mail spool file */
+char	*mailboxdir = nil;				/* nil == /mail/box/$user */
+char *fsname;						/* filesystem for mailboxdir/mboxname is at maildir/fsname */
+char	*user;
+char	*outgoing;
+
+Window	*wbox;
+Message	mbox;
+Message	replies;
+char		*home;
+int		plumbsendfd;
+int		plumbseemailfd;
+int		plumbshowmailfd;
+int		plumbsendmailfd;
+Channel	*cplumb;
+Channel	*cplumbshow;
+Channel	*cplumbsend;
+int		wctlfd;
+void		mainctl(void*);
+void		plumbproc(void*);
+void		plumbshowproc(void*);
+void		plumbsendproc(void*);
+void		plumbthread(void);
+void		plumbshowthread(void*);
+void		plumbsendthread(void*);
+
+int			shortmenu;
+
+void
+usage(void)
+{
+	fprint(2, "usage: Mail [-sS] [mailboxname [directoryname]]\n");
+	threadexitsall("usage");
+}
+
+void
+removeupasfs(void)
+{
+	char buf[256];
+
+	if(strcmp(mboxname, "mbox") == 0)
+		return;
+	snprint(buf, sizeof buf, "close %s", mboxname);
+	write(mbox.ctlfd, buf, strlen(buf));
+}
+
+int
+ismaildir(char *s)
+{
+	char buf[256];
+	Dir *d;
+	int ret;
+
+	snprint(buf, sizeof buf, "%s%s", maildir, s);
+	d = dirstat(buf);
+	if(d == nil)
+		return 0;
+	ret = d->qid.type & QTDIR;
+	free(d);
+	return ret;
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+	char *s, *name;
+	char err[ERRMAX], cmd[256];
+	int i, newdir;
+
+	doquote = needsrcquote;
+	quotefmtinstall();
+
+	/* open these early so we won't miss notification of new mail messages while we read mbox */
+	plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
+	plumbseemailfd = plumbopen("seemail", OREAD|OCEXEC);
+	plumbshowmailfd = plumbopen("showmail", OREAD|OCEXEC);
+
+	shortmenu = 0;
+	ARGBEGIN{
+	case 's':
+		shortmenu = 1;
+		break;
+	case 'S':
+		shortmenu = 2;
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	name = "mbox";
+
+	/* bind the terminal /mail/fs directory over the local one */
+	if(access(maildir, 0)<0 && access(mailtermdir, 0)==0)
+		bind(mailtermdir, maildir, MAFTER);
+
+	newdir = 1;
+	if(argc > 0){
+		i = strlen(argv[0]);
+		if(argc>2 || i==0)
+			usage();
+		/* see if the name is that of an existing /mail/fs directory */
+		if(argc==1 && strchr(argv[0], '/')==0 && ismaildir(argv[0])){
+			name = argv[0];
+			mboxname = eappend(estrdup(maildir), "", name);
+			newdir = 0;
+		}else{
+			if(argv[0][i-1] == '/')
+				argv[0][i-1] = '\0';
+			s = strrchr(argv[0], '/');
+			if(s == nil)
+				mboxname = estrdup(argv[0]);
+			else{
+				*s++ = '\0';
+				if(*s == '\0')
+					usage();
+				mailboxdir = argv[0];
+				mboxname = estrdup(s);
+			}
+			if(argc > 1)
+				name = argv[1];
+			else
+				name = mboxname;
+		}
+	}
+
+	user = getenv("user");
+	if(user == nil)
+		user = "none";
+	if(mailboxdir == nil)
+		mailboxdir = estrstrdup("/mail/box/", user);
+	outgoing = estrstrdup(mailboxdir, "/outgoing");
+
+	s = estrstrdup(maildir, "ctl");
+	mbox.ctlfd = open(s, ORDWR|OCEXEC);
+	if(mbox.ctlfd < 0)
+		error("Mail: can't open %s: %r\n", s);
+
+	fsname = estrdup(name);
+	if(newdir && argc > 0){
+		s = emalloc(5+strlen(mailboxdir)+strlen(mboxname)+strlen(name)+10+1);
+		for(i=0; i<10; i++){
+			sprint(s, "open %s/%s %s", mailboxdir, mboxname, fsname);
+			if(write(mbox.ctlfd, s, strlen(s)) >= 0)
+				break;
+			err[0] = '\0';
+			errstr(err, sizeof err);
+			if(strstr(err, "mbox name in use") == nil)
+				error("Mail: can't create directory %s for mail: %s\n", name, err);
+			free(fsname);
+			fsname = emalloc(strlen(name)+10);
+			sprint(fsname, "%s-%d", name, i);
+		}
+		if(i == 10)
+			error("Mail: can't open %s/%s: %r", mailboxdir, mboxname);
+		free(s);
+	}
+
+	s = estrstrdup(fsname, "/");
+	mbox.name = estrstrdup(maildir, s);
+	mbox.level= 0;
+	readmbox(&mbox, maildir, s);
+	home = getenv("home");
+	if(home == nil)
+		home = "/";
+
+	wbox = newwindow();
+	winname(wbox, mbox.name);
+	wintagwrite(wbox, "Put Mail Delmesg ", 3+1+4+1+7+1);
+	threadcreate(mainctl, wbox, STACK);
+	snprint(cmd, sizeof cmd, "Mail %s", name);
+	winsetdump(wbox, "/acme/mail", cmd);
+	mbox.w = wbox;
+
+	mesgmenu(wbox, &mbox);
+	winclean(wbox);
+
+	wctlfd = open("/dev/wctl", OWRITE|OCEXEC);	/* for acme window */
+	cplumb = chancreate(sizeof(Plumbmsg*), 0);
+	cplumbshow = chancreate(sizeof(Plumbmsg*), 0);
+	if(strcmp(name, "mbox") == 0){
+		/*
+		 * Avoid creating multiple windows to send mail by only accepting
+		 * sendmail plumb messages if we're reading the main mailbox.
+		 */
+		plumbsendmailfd = plumbopen("sendmail", OREAD|OCEXEC);
+		cplumbsend = chancreate(sizeof(Plumbmsg*), 0);
+		proccreate(plumbsendproc, nil, STACK);
+		threadcreate(plumbsendthread, nil, STACK);
+	}
+	/* start plumb reader as separate proc ... */
+	proccreate(plumbproc, nil, STACK);
+	proccreate(plumbshowproc, nil, STACK);
+	threadcreate(plumbshowthread, nil, STACK);
+	/* ... and use this thread to read the messages */
+	plumbthread();
+}
+
+void
+plumbproc(void*)
+{
+	Plumbmsg *m;
+
+	threadsetname("plumbproc");
+	for(;;){
+		m = plumbrecv(plumbseemailfd);
+		sendp(cplumb, m);
+		if(m == nil)
+			threadexits(nil);
+	}
+}
+
+void
+plumbshowproc(void*)
+{
+	Plumbmsg *m;
+
+	threadsetname("plumbshowproc");
+	for(;;){
+		m = plumbrecv(plumbshowmailfd);
+		sendp(cplumbshow, m);
+		if(m == nil)
+			threadexits(nil);
+	}
+}
+
+void
+plumbsendproc(void*)
+{
+	Plumbmsg *m;
+
+	threadsetname("plumbsendproc");
+	for(;;){
+		m = plumbrecv(plumbsendmailfd);
+		sendp(cplumbsend, m);
+		if(m == nil)
+			threadexits(nil);
+	}
+}
+
+void
+newmesg(char *name, char *digest)
+{
+	Dir *d;
+
+	if(strncmp(name, mbox.name, strlen(mbox.name)) != 0)
+		return;	/* message is about another mailbox */
+	if(mesglookupfile(&mbox, name, digest) != nil)
+		return;
+	d = dirstat(name);
+	if(d == nil)
+		return;
+	if(mesgadd(&mbox, mbox.name, d, digest))
+		mesgmenunew(wbox, &mbox);
+	free(d);
+}
+
+void
+showmesg(char *name, char *digest)
+{
+	char *n;
+
+	if(strncmp(name, mbox.name, strlen(mbox.name)) != 0)
+		return;	/* message is about another mailbox */
+	n = estrdup(name+strlen(mbox.name));
+	if(n[strlen(n)-1] != '/')
+		n = egrow(n, "/", nil);
+	mesgopen(&mbox, mbox.name, name+strlen(mbox.name), nil, 1, digest);
+	free(n);
+}
+
+void
+delmesg(char *name, char *digest, int dodel)
+{
+	Message *m;
+
+	m = mesglookupfile(&mbox, name, digest);
+	if(m != nil){
+		mesgmenumarkdel(wbox, &mbox, m, 0);
+		if(dodel)
+			m->writebackdel = 1;
+	}
+}
+
+void
+plumbthread(void)
+{
+	Plumbmsg *m;
+	Plumbattr *a;
+	char *type, *digest;
+
+	threadsetname("plumbthread");
+	while((m = recvp(cplumb)) != nil){
+		a = m->attr;
+		digest = plumblookup(a, "digest");
+		type = plumblookup(a, "mailtype");
+		if(type == nil)
+			fprint(2, "Mail: plumb message with no mailtype attribute\n");
+		else if(strcmp(type, "new") == 0)
+			newmesg(m->data, digest);
+		else if(strcmp(type, "delete") == 0)
+			delmesg(m->data, digest, 0);
+		else
+			fprint(2, "Mail: unknown plumb attribute %s\n", type);
+		plumbfree(m);
+	}
+	threadexits(nil);
+}
+
+void
+plumbshowthread(void*)
+{
+	Plumbmsg *m;
+
+	threadsetname("plumbshowthread");
+	while((m = recvp(cplumbshow)) != nil){
+		showmesg(m->data, plumblookup(m->attr, "digest"));
+		plumbfree(m);
+	}
+	threadexits(nil);
+}
+
+void
+plumbsendthread(void*)
+{
+	Plumbmsg *m;
+
+	threadsetname("plumbsendthread");
+	while((m = recvp(cplumbsend)) != nil){
+		mkreply(nil, "Mail", m->data, m->attr, nil);
+		plumbfree(m);
+	}
+	threadexits(nil);
+}
+
+int
+mboxcommand(Window *w, char *s)
+{
+	char *args[10], **targs;
+	Message *m, *next;
+	int ok, nargs, i, j;
+	char buf[128];
+
+	nargs = tokenize(s, args, nelem(args));
+	if(nargs == 0)
+		return 0;
+	if(strcmp(args[0], "Mail") == 0){
+		if(nargs == 1)
+			mkreply(nil, "Mail", "", nil, nil);
+		else
+			mkreply(nil, "Mail", args[1], nil, nil);
+		return 1;
+	}
+	if(strcmp(s, "Del") == 0){
+		if(mbox.dirty){
+			mbox.dirty = 0;
+			fprint(2, "mail: mailbox not written\n");
+			return 1;
+		}
+		ok = 1;
+		for(m=mbox.head; m!=nil; m=next){
+			next = m->next;
+			if(m->w){
+				if(windel(m->w, 0))
+					m->w = nil;
+				else
+					ok = 0;
+			}
+		}
+		for(m=replies.head; m!=nil; m=next){
+			next = m->next;
+			if(m->w){
+				if(windel(m->w, 0))
+					m->w = nil;
+				else
+					ok = 0;
+			}
+		}
+		if(ok){
+			windel(w, 1);
+			removeupasfs();
+			threadexitsall(nil);
+		}
+		return 1;
+	}
+	if(strcmp(s, "Put") == 0){
+		rewritembox(wbox, &mbox);
+		return 1;
+	}
+	if(strcmp(s, "Delmesg") == 0){
+		if(nargs > 1){
+			for(i=1; i<nargs; i++){
+				snprint(buf, sizeof buf, "%s%s", mbox.name, args[i]);
+				delmesg(buf, nil, 1);
+			}
+		}
+		s = winselection(w);
+		if(s == nil)
+			return 1;
+		nargs = 1;
+		for(i=0; s[i]; i++)
+			if(s[i] == '\n')
+				nargs++;
+		targs = emalloc(nargs*sizeof(char*));	/* could be too many for a local array */
+		nargs = getfields(s, targs, nargs, 1, "\n");
+		for(i=0; i<nargs; i++){
+			if(!isdigit(targs[i][0]))
+				continue;
+			j = atoi(targs[i]);	/* easy way to parse the number! */
+			if(j == 0)
+				continue;
+			snprint(buf, sizeof buf, "%s%d", mbox.name, j);
+			delmesg(buf, nil, 1);
+		}
+		free(s);
+		free(targs);
+		return 1;
+	}
+	return 0;
+}
+
+void
+mainctl(void *v)
+{
+	Window *w;
+	Event *e, *e2, *eq, *ea;
+	int na, nopen;
+	char *s, *t, *buf;
+
+	w = v;
+	proccreate(wineventproc, w, STACK);
+
+	for(;;){
+		e = recvp(w->cevent);
+		switch(e->c1){
+		default:
+		Unknown:
+			print("unknown message %c%c\n", e->c1, e->c2);
+			break;
+	
+		case 'E':	/* write to body; can't affect us */
+			break;
+	
+		case 'F':	/* generated by our actions; ignore */
+			break;
+	
+		case 'K':	/* type away; we don't care */
+			break;
+	
+		case 'M':
+			switch(e->c2){
+			case 'x':
+			case 'X':
+				ea = nil;
+				e2 = nil;
+				if(e->flag & 2)
+					e2 = recvp(w->cevent);
+				if(e->flag & 8){
+					ea = recvp(w->cevent);
+					na = ea->nb;
+					recvp(w->cevent);
+				}else
+					na = 0;
+				s = e->b;
+				/* if it's a known command, do it */
+				if((e->flag&2) && e->nb==0)
+					s = e2->b;
+				if(na){
+					t = emalloc(strlen(s)+1+na+1);
+					sprint(t, "%s %s", s, ea->b);
+					s = t;
+				}
+				/* if it's a long message, it can't be for us anyway */
+				if(!mboxcommand(w, s))	/* send it back */
+					winwriteevent(w, e);
+				if(na)
+					free(s);
+				break;
+	
+			case 'l':
+			case 'L':
+				buf = nil;
+				eq = e;
+				if(e->flag & 2){
+					e2 = recvp(w->cevent);
+					eq = e2;
+				}
+				s = eq->b;
+				if(eq->q1>eq->q0 && eq->nb==0){
+					buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
+					winread(w, eq->q0, eq->q1, buf);
+					s = buf;
+				}
+				nopen = 0;
+				do{
+					/* skip 'deleted' string if present' */
+					if(strncmp(s, deleted, strlen(deleted)) == 0)
+						s += strlen(deleted);
+					/* skip mail box name if present */
+					if(strncmp(s, mbox.name, strlen(mbox.name)) == 0)
+						s += strlen(mbox.name);
+					nopen += mesgopen(&mbox, mbox.name, s, nil, 0, nil);
+					while(*s!='\0' && *s++!='\n')
+						;
+				}while(*s);
+				if(nopen == 0)	/* send it back */
+					winwriteevent(w, e);
+				free(buf);
+				break;
+	
+			case 'I':	/* modify away; we don't care */
+			case 'D':
+			case 'd':
+			case 'i':
+				break;
+	
+			default:
+				goto Unknown;
+			}
+		}
+	}
+}
+

+ 1306 - 0
acme/mail/src/mesg.c

@@ -0,0 +1,1306 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <ctype.h>
+#include <plumb.h>
+#include "dat.h"
+
+enum
+{
+	DIRCHUNK = 32*sizeof(Dir)
+};
+
+char	regexchars[] = "\\/[].+?()*^$";
+char	deleted[] = "(deleted)-";
+char	deletedrx[] = "\\(deleted\\)-";
+char	deletedrx01[] = "(\\(deleted\\)-)?";
+char	deletedaddr[] = "-#0;/^\\(deleted\\)-/";
+
+struct{
+	char	*type;
+	char	*port;
+	char *suffix;
+} ports[] = {
+	"text/",			"edit",	".txt", /* must be first for plumbport() */
+	"image/gif",		"image",	".gif",
+	"image/jpeg",		"image",	".jpg",
+	"image/jpeg",		"image",	".jpeg",
+	"application/postscript",	"postscript",	".ps",
+	"application/pdf",	"postscript",	".pdf",
+	"application/msword",	"msword",	".doc",
+	"application/rtf",	"msword",	".rtf",
+	nil,	nil
+};
+
+char *goodtypes[] = {
+	"text",
+	"text/plain",
+	"message/rfc822",
+	"text/richtext",
+	"text/tab-separated-values",
+	"application/octet-stream",
+	nil,
+};
+
+struct{
+	char *type;
+	char	*ext;
+} exts[] = {
+	"image/gif",	".gif",
+	"image/jpeg",	".jpg",
+	nil, nil
+};
+
+char *okheaders[] =
+{
+	"From:",
+	"Date:",
+	"To:",
+	"CC:",
+	"Subject:",
+	nil
+};
+
+char*
+line(char *data, char **pp)
+{
+	char *p, *q;
+
+	for(p=data; *p!='\0' && *p!='\n'; p++)
+		;
+	if(*p == '\n')
+		*pp = p+1;
+	else
+		*pp = p;
+	q = emalloc(p-data + 1);
+	memmove(q, data, p-data);
+	return q;
+}
+
+void
+scanheaders(Message *m, char *dir)
+{
+	char *s, *t, *u, *f;
+
+	s = f = readfile(dir, "header", nil);
+	if(s != nil)
+		while(*s){
+			t = line(s, &s);
+			if(strncmp(t, "From: ", 6) == 0){
+				m->fromcolon = estrdup(t+6);
+				/* remove all quotes; they're ugly and irregular */
+				for(u=m->fromcolon; *u; u++)
+					if(*u == '"')
+						memmove(u, u+1, strlen(u));
+			}
+			if(strncmp(t, "Subject: ", 9) == 0)
+				m->subject = estrdup(t+9);
+			free(t);
+		}
+	if(m->fromcolon == nil)
+		m->fromcolon = estrdup(m->from);
+	free(f);
+}
+
+int
+loadinfo(Message *m, char *dir)
+{
+	int n;
+	char *data, *p, *s;
+
+	data = readfile(dir, "info", &n);
+	if(data == nil)
+		return 0;
+	m->from = line(data, &p);
+	scanheaders(m, dir);	/* depends on m->from being set */
+	m->to = line(p, &p);
+	m->cc = line(p, &p);
+	m->replyto = line(p, &p);
+	m->date = line(p, &p);
+	s = line(p, &p);
+	if(m->subject == nil)
+		m->subject = s;
+	else
+		free(s);
+	m->type = line(p, &p);
+	m->disposition = line(p, &p);
+	m->filename = line(p, &p);
+	m->digest = line(p, &p);
+	free(data);
+	return 1;
+}
+
+int
+isnumeric(char *s)
+{
+	while(*s){
+		if(!isdigit(*s))
+			return 0;
+		s++;
+	}
+	return 1;
+}
+
+Dir*
+loaddir(char *name, int *np)
+{
+	int fd;
+	Dir *dp;
+
+	fd = open(name, OREAD);
+	if(fd < 0)
+		return nil;
+	*np = dirreadall(fd, &dp);
+	close(fd);
+	return dp;
+}
+
+void
+readmbox(Message *mbox, char *dir, char *subdir)
+{
+	char *name;
+	Dir *d, *dirp;
+	int i, n;
+
+	name = estrstrdup(dir, subdir);
+	dirp = loaddir(name, &n);
+	mbox->recursed = 1;
+	if(dirp)
+		for(i=0; i<n; i++){
+			d = &dirp[i];
+			if(isnumeric(d->name))
+				mesgadd(mbox, name, d, nil);
+		}
+	free(dirp);
+	free(name);
+}
+
+/* add message to box, in increasing numerical order */
+int
+mesgadd(Message *mbox, char *dir, Dir *d, char *digest)
+{
+	Message *m;
+	char *name;
+	int loaded;
+
+	m = emalloc(sizeof(Message));
+	m->name = estrstrdup(d->name, "/");
+	m->next = nil;
+	m->prev = mbox->tail;
+	m->level= mbox->level+1;
+	m->recursed = 0;
+	name = estrstrdup(dir, m->name);
+	loaded = loadinfo(m, name);
+	free(name);
+	/* if two upas/fs are running, we can get misled, so check digest before accepting message */
+	if(loaded==0 || (digest!=nil && m->digest!=nil && strcmp(digest, m->digest)!=0)){
+		mesgfreeparts(m);
+		free(m);
+		return 0;
+	}
+	if(mbox->tail != nil)
+		mbox->tail->next = m;
+	mbox->tail = m;
+	if(mbox->head == nil)
+		mbox->head = m;
+
+	if (m->level != 1){
+		m->recursed = 1;
+		readmbox(m, dir, m->name); 
+	}
+	return 1;
+}
+
+int
+thisyear(char *year)
+{
+	static char now[10];
+	char *s;
+
+	if(now[0] == '\0'){
+		s = ctime(time(nil));
+		strcpy(now, s+24);
+	}
+	return strncmp(year, now, 4) == 0;
+}
+
+char*
+stripdate(char *as)
+{
+	int n;
+	char *s, *fld[10];
+
+	as = estrdup(as);
+	s = estrdup(as);
+	n = tokenize(s, fld, 10);
+	if(n > 5){
+		sprint(as, "%.3s ", fld[0]);	/* day */
+		/* some dates have 19 Apr, some Apr 19 */
+		if(strlen(fld[1])<4 && isnumeric(fld[1]))
+			sprint(as+strlen(as), "%.3s %.3s ", fld[1], fld[2]);	/* date, month */
+		else
+			sprint(as+strlen(as), "%.3s %.3s ", fld[2], fld[1]);	/* date, month */
+		/* do we use time or year?  depends on whether year matches this one */
+		if(thisyear(fld[5])){
+			if(strchr(fld[3], ':') != nil)
+				sprint(as+strlen(as), "%.5s ", fld[3]);	/* time */
+			else if(strchr(fld[4], ':') != nil)
+				sprint(as+strlen(as), "%.5s ", fld[4]);	/* time */
+		}else
+			sprint(as+strlen(as), "%.4s ", fld[5]);	/* year */
+	}
+	free(s);
+	return as;
+}
+
+char*
+readfile(char *dir, char *name, int *np)
+{
+	char *file, *data;
+	int fd, len;
+	Dir *d;
+
+	if(np != nil)
+		*np = 0;
+	file = estrstrdup(dir, name);
+	fd = open(file, OREAD);
+	if(fd < 0)
+		return nil;
+	d = dirfstat(fd);
+	free(file);
+	len = 0;
+	if(d != nil)
+		len = d->length;
+	free(d);
+	data = emalloc(len+1);
+	read(fd, data, len);
+	close(fd);
+	if(np != nil)
+		*np = len;
+	return data;
+}
+
+char*
+info(Message *m, int ind, int ogf)
+{
+	char *i;
+	int j, len, lens;
+	char *p;
+	char fmt[80], s[80];
+
+	if (ogf)
+		p=m->to;
+	else
+		p=m->fromcolon;
+
+	if(ind==0 && shortmenu){
+		len = 30;
+		lens = 30;
+		if(shortmenu > 1){
+			len = 10;
+			lens = 25;
+		}
+		if(ind==0 && m->subject[0]=='\0'){
+			snprint(fmt, sizeof fmt, " %%-%d.%ds", len, len);
+			snprint(s, sizeof s, fmt, p);
+		}else{
+			snprint(fmt, sizeof fmt, " %%-%d.%ds  %%-%d.%ds", len, len, lens, lens);
+			snprint(s, sizeof s, fmt, p, m->subject);
+		}
+		i = estrdup(s);
+
+		return i;
+	} 
+
+	i = estrdup("");
+	i = eappend(i, "\t", p);
+	i = egrow(i, "\t", stripdate(m->date));
+	if(ind == 0){
+		if(strcmp(m->type, "text")!=0 && strncmp(m->type, "text/", 5)!=0 && 
+		   strncmp(m->type, "multipart/", 10)!=0)
+			i = egrow(i, "\t(", estrstrdup(m->type, ")"));
+	}else if(strncmp(m->type, "multipart/", 10) != 0)
+		i = egrow(i, "\t(", estrstrdup(m->type, ")"));
+	if(m->subject[0] != '\0'){
+		i = eappend(i, "\n", nil);
+		for(j=0; j<ind; j++)
+			i = eappend(i, "\t", nil);
+		i = eappend(i, "\t", m->subject);
+	}
+	return i;
+}
+
+void
+mesgmenu0(Window *w, Message *mbox, char *realdir, char *dir, int ind, Biobuf *fd, int onlyone, int dotail)
+{
+	int i;
+	Message *m;
+	char *name, *tmp;
+	int ogf=0;
+
+	if (strcmp(realdir, "/mail/fs/outgoing/") == 0)
+		ogf=1;
+
+	/* show mail box in reverse order, pieces in forward order */
+	if(ind > 0)
+		m = mbox->head;
+	else
+		m = mbox->tail;
+	while(m != nil){
+		for(i=0; i<ind; i++)
+			Bprint(fd, "\t");
+		if(ind != 0)
+			Bprint(fd, "  ");
+		name = estrstrdup(dir, m->name);
+		tmp = info(m, ind, ogf);
+		Bprint(fd, "%s%s\n", name, tmp);
+		free(tmp);
+		if(dotail && m->tail)
+			mesgmenu0(w, m, realdir, name, ind+1, fd, 0, dotail);
+		free(name);
+		if(ind)
+			m = m->next;
+		else
+			m = m->prev;
+		if(onlyone)
+			m = nil;
+	}
+}
+
+void
+mesgmenu(Window *w, Message *mbox)
+{
+	winopenbody(w, OWRITE);
+	mesgmenu0(w, mbox, mbox->name, "", 0, w->body, 0, !shortmenu);
+	winclosebody(w);
+}
+
+/* one new message has arrived, as mbox->tail */
+void
+mesgmenunew(Window *w, Message *mbox)
+{
+	Biobuf *b;
+
+	winselect(w, "0", 0);
+	w->data = winopenfile(w, "data");
+	b = emalloc(sizeof(Biobuf));
+	Binit(b, w->data, OWRITE);
+	mesgmenu0(w, mbox, mbox->name, "", 0, b, 1, !shortmenu);
+	Bterm(b);
+	free(b);
+	if(!mbox->dirty)
+		winclean(w);
+	/* select tag line plus following indented lines, but not final newline (it's distinctive) */
+	winselect(w, "0/.*\\n((\t.*\\n)*\t.*)?/", 1);
+	close(w->addr);
+	close(w->data);
+	w->addr = -1;
+	w->data = -1;
+}
+
+char*
+name2regexp(char *prefix, char *s)
+{
+	char *buf, *p, *q;
+
+	buf = emalloc(strlen(prefix)+2*strlen(s)+50);	/* leave room to append more */
+	p = buf;
+	*p++ = '0';
+	*p++ = '/';
+	*p++ = '^';
+	strcpy(p, prefix);
+	p += strlen(prefix);
+	for(q=s; *q!='\0'; q++){
+		if(strchr(regexchars, *q) != nil)
+			*p++ = '\\';
+		*p++ = *q;
+	}
+	*p++ = '/';
+	*p = '\0';
+	return buf;
+}
+
+void
+mesgmenumarkdel(Window *w, Message *mbox, Message *m, int writeback)
+{
+	char *buf;
+
+
+	if(m->deleted)
+		return;
+	m->writebackdel = writeback;
+	if(w->data < 0)
+		w->data = winopenfile(w, "data");
+	buf = name2regexp("", m->name);
+	strcat(buf, "-#0");
+	if(winselect(w, buf, 1))
+		write(w->data, deleted, 10);
+	free(buf);
+	close(w->data);
+	close(w->addr);
+	w->addr = w->data = -1;
+	mbox->dirty = 1;
+	m->deleted = 1;
+}
+
+void
+mesgmenumarkundel(Window *w, Message*, Message *m)
+{
+	char *buf;
+
+	if(m->deleted == 0)
+		return;
+	if(w->data < 0)
+		w->data = winopenfile(w, "data");
+	buf = name2regexp(deletedrx, m->name);
+	if(winselect(w, buf, 1))
+		if(winsetaddr(w, deletedaddr, 1))
+			write(w->data, "", 0);
+	free(buf);
+	close(w->data);
+	close(w->addr);
+	w->addr = w->data = -1;
+	m->deleted = 0;
+}
+
+void
+mesgmenudel(Window *w, Message *mbox, Message *m)
+{
+	char *buf;
+
+	if(w->data < 0)
+		w->data = winopenfile(w, "data");
+	buf = name2regexp(deletedrx, m->name);
+	if(winsetaddr(w, buf, 1) && winsetaddr(w, ".,./.*\\n(\t.*\\n)*/", 1))
+		write(w->data, "", 0);
+	free(buf);
+	close(w->data);
+	close(w->addr);
+	w->addr = w->data = -1;
+	mbox->dirty = 1;
+	m->deleted = 1;
+}
+
+void
+mesgmenumark(Window *w, char *which, char *mark)
+{
+	char *buf;
+
+	if(w->data < 0)
+		w->data = winopenfile(w, "data");
+	buf = name2regexp(deletedrx01, which);
+	if(winsetaddr(w, buf, 1) && winsetaddr(w, "+0-#1", 1))	/* go to end of line */
+		write(w->data, mark, strlen(mark));
+	free(buf);
+	close(w->data);
+	close(w->addr);
+	w->addr = w->data = -1;
+	if(!mbox.dirty)
+		winclean(w);
+}
+
+void
+mesgfreeparts(Message *m)
+{
+	free(m->name);
+	free(m->replyname);
+	free(m->fromcolon);
+	free(m->from);
+	free(m->to);
+	free(m->cc);
+	free(m->replyto);
+	free(m->date);
+	free(m->subject);
+	free(m->type);
+	free(m->disposition);
+	free(m->filename);
+	free(m->digest);
+}
+
+void
+mesgdel(Message *mbox, Message *m)
+{
+	Message *n, *next;
+
+	if(m->opened)
+		error("Mail: internal error: deleted message still open in mesgdel\n");
+	/* delete subparts */
+	for(n=m->head; n!=nil; n=next){
+		next = n->next;
+		mesgdel(m, n);
+	}
+	/* remove this message from list */
+	if(m->next)
+		m->next->prev = m->prev;
+	else
+		mbox->tail = m->prev;
+	if(m->prev)
+		m->prev->next = m->next;
+	else
+		mbox->head = m->next;
+
+	mesgfreeparts(m);
+}
+
+int
+mesgsave(Message *m, char *s)
+{
+	int ofd, n, k, ret;
+	char *t, *raw, *unixheader, *all;
+
+	t = estrstrdup(mbox.name, m->name);
+	raw = readfile(t, "raw", &n);
+	unixheader = readfile(t, "unixheader", &k);
+	if(raw==nil || unixheader==nil){
+		fprint(2, "Mail: can't read %s: %r\n", t);
+		free(t);
+		return 0;
+	}
+	free(t);
+
+	all = emalloc(n+k+1);
+	memmove(all, unixheader, k);
+	memmove(all+k, raw, n);
+	memmove(all+k+n, "\n", 1);
+	n = k+n+1;
+	free(unixheader);
+	free(raw);
+	ret = 1;
+	s = estrdup(s);
+	if(s[0] != '/')
+		s = egrow(estrdup(mailboxdir), "/", s);
+	ofd = open(s, OWRITE);
+	if(ofd < 0){
+		fprint(2, "Mail: can't open %s: %r\n", s);
+		ret = 0;
+	}else if(seek(ofd, 0LL, 2)<0 || write(ofd, all, n)!=n){
+		fprint(2, "Mail: save failed: can't write %s: %r\n", s);
+		ret = 0;
+	}
+	free(all);
+	close(ofd);
+	free(s);
+	return ret;
+}
+
+int
+mesgcommand(Message *m, char *cmd)
+{
+	char *s;
+	char *args[10];
+	int ok, ret, nargs;
+
+	s = cmd;
+	ret = 1;
+	nargs = tokenize(s, args, nelem(args));
+	if(nargs == 0)
+		return 0;
+	if(strcmp(args[0], "Post") == 0){
+		mesgsend(m);
+		goto Return;
+	}
+	if(strncmp(args[0], "Save", 4) == 0){
+		if(m->isreply)
+			goto Return;
+		s = estrdup("\t[saved");
+		if(nargs==1 || strcmp(args[1], "")==0){
+			ok = mesgsave(m, "stored");
+		}else{
+			ok = mesgsave(m, args[1]);
+			s = eappend(s, " ", args[1]);
+		}
+		if(ok){
+			s = egrow(s, "]", nil);
+			mesgmenumark(mbox.w, m->name, s);
+		}
+		free(s);
+		goto Return;
+	}
+	if(strcmp(args[0], "Reply")==0){
+		if(nargs>=2 && strcmp(args[1], "all")==0)
+			mkreply(m, "Replyall", nil, nil, nil);
+		else
+			mkreply(m, "Reply", nil, nil, nil);
+		goto Return;
+	}
+	if(strcmp(args[0], "Q") == 0){
+		s = winselection(m->w);	/* will be freed by mkreply */
+		if(nargs>=3 && strcmp(args[1], "Reply")==0 && strcmp(args[2], "all")==0)
+			mkreply(m, "QReplyall", nil, nil, s);
+		else
+			mkreply(m, "QReply", nil, nil, s);
+		goto Return;
+	}
+	if(strcmp(args[0], "Del") == 0){
+		if(windel(m->w, 0)){
+			chanfree(m->w->cevent);
+			free(m->w);
+			m->w = nil;
+			if(m->isreply)
+				delreply(m);
+			else{
+				m->opened = 0;
+				m->tagposted = 0;
+			}
+			free(cmd);
+			threadexits(nil);
+		}
+		goto Return;
+	}
+	if(strcmp(args[0], "Delmesg") == 0){
+		if(!m->isreply){
+			mesgmenumarkdel(wbox, &mbox, m, 1);
+			free(cmd);	/* mesgcommand might not return */
+			mesgcommand(m, estrdup("Del"));
+			return 1;
+		}
+		goto Return;
+	}
+	if(strcmp(args[0], "UnDelmesg") == 0){
+		if(!m->isreply && m->deleted)
+			mesgmenumarkundel(wbox, &mbox, m);
+		goto Return;
+	}
+//	if(strcmp(args[0], "Headers") == 0){
+//		m->showheaders();
+//		return True;
+//	}
+
+	ret = 0;
+
+    Return:
+	free(cmd);
+	return ret;
+}
+
+void
+mesgtagpost(Message *m)
+{
+	if(m->tagposted)
+		return;
+	wintagwrite(m->w, " Post", 5);
+	m->tagposted = 1;
+}
+
+/* need to expand selection more than default word */
+#pragma varargck argpos eval 2
+
+long
+eval(Window *w, char *s, ...)
+{
+	char buf[64];
+	va_list arg;
+
+	va_start(arg, s);
+	vsnprint(buf, sizeof buf, s, arg);
+	va_end(arg);
+
+	if(winsetaddr(w, buf, 1)==0)
+		return -1;
+
+	if(pread(w->addr, buf, 24, 0) != 24)
+		return -1;
+	return strtol(buf, 0, 10);
+}
+
+int
+isemail(char *s)
+{
+	int nat;
+
+	nat = 0;
+	for(; *s; s++)
+		if(*s == '@')
+			nat++;
+		else if(!isalpha(*s) && !isdigit(*s) && !strchr("_.-+/", *s))
+			return 0;
+	return nat==1;
+}
+
+char addrdelim[] =  "/[ \t\\n<>()\\[\\]]/";
+char*
+expandaddr(Window *w, Event *e)
+{
+	char *s;
+	long q0, q1;
+
+	if(e->q0 != e->q1)	/* cannot happen */
+		return nil;
+
+	q0 = eval(w, "#%d-%s", e->q0, addrdelim);
+	if(q0 == -1)	/* bad char not found */
+		q0 = 0;
+	else			/* increment past bad char */
+		q0++;
+
+	q1 = eval(w, "#%d+%s", e->q0, addrdelim);
+	if(q1 < 0){
+		q1 = eval(w, "$");
+		if(q1 < 0)
+			return nil;
+	}
+	if(q0 >= q1)
+		return nil;
+	s = emalloc((q1-q0)*UTFmax+1);
+	winread(w, q0, q1, s);
+	return s;
+}
+
+int
+replytoaddr(Window *w, Message *m, Event *e, char *s)
+{
+	int did;
+	char *buf;
+	Plumbmsg *pm;
+
+	buf = nil;
+	did = 0;
+	if(e->flag & 2){
+		/* autoexpanded; use our own bigger expansion */
+		buf = expandaddr(w, e);
+		if(buf == nil)
+			return 0;
+		s = buf;
+	}
+	if(isemail(s)){
+		did = 1;
+		pm = emalloc(sizeof(Plumbmsg));
+		pm->src = estrdup("Mail");
+		pm->dst = estrdup("sendmail");
+		pm->data = estrdup(s);
+		pm->ndata = -1;
+		if(m->subject && m->subject[0]){
+			pm->attr = emalloc(sizeof(Plumbattr));
+			pm->attr->name = estrdup("Subject");
+			if(tolower(m->subject[0]) != 'r' || tolower(m->subject[1]) != 'e' || m->subject[2] != ':')
+				pm->attr->value = estrstrdup("Re: ", m->subject);
+			else
+				pm->attr->value = estrdup(m->subject);
+			pm->attr->next = nil;
+		}
+		if(plumbsend(plumbsendfd, pm) < 0)
+			fprint(2, "error writing plumb message: %r\n");
+		plumbfree(pm);
+	}
+	free(buf);
+	return did;
+}
+
+
+void
+mesgctl(void *v)
+{
+	Message *m;
+	Window *w;
+	Event *e, *eq, *e2, *ea;
+	int na, nopen, i, j;
+	char *os, *s, *t, *buf;
+
+	m = v;
+	w = m->w;
+	threadsetname("mesgctl");
+	proccreate(wineventproc, w, STACK);
+	for(;;){
+		e = recvp(w->cevent);
+		switch(e->c1){
+		default:
+		Unk:
+			print("unknown message %c%c\n", e->c1, e->c2);
+			break;
+
+		case 'E':	/* write to body; can't affect us */
+			break;
+
+		case 'F':	/* generated by our actions; ignore */
+			break;
+
+		case 'K':	/* type away; we don't care */
+		case 'M':
+			switch(e->c2){
+			case 'x':	/* mouse only */
+			case 'X':
+				ea = nil;
+				eq = e;
+				if(e->flag & 2){
+					e2 = recvp(w->cevent);
+					eq = e2;
+				}
+				if(e->flag & 8){
+					ea = recvp(w->cevent);
+					recvp(w->cevent);
+					na = ea->nb;
+				}else
+					na = 0;
+				if(eq->q1>eq->q0 && eq->nb==0){
+					s = emalloc((eq->q1-eq->q0)*UTFmax+1);
+					winread(w, eq->q0, eq->q1, s);
+				}else
+					s = estrdup(eq->b);
+				if(na){
+					t = emalloc(strlen(s)+1+na+1);
+					sprint(t, "%s %s", s, ea->b);
+					free(s);
+					s = t;
+				}
+				if(!mesgcommand(m, s))	/* send it back */
+					winwriteevent(w, e);
+				break;
+
+			case 'l':	/* mouse only */
+			case 'L':
+				buf = nil;
+				eq = e;
+				if(e->flag & 2){
+					e2 = recvp(w->cevent);
+					eq = e2;
+				}
+				s = eq->b;
+				if(eq->q1>eq->q0 && eq->nb==0){
+					buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
+					winread(w, eq->q0, eq->q1, buf);
+					s = buf;
+				}
+				os = s;
+				nopen = 0;
+				do{
+					/* skip mail box name if present */
+					if(strncmp(s, mbox.name, strlen(mbox.name)) == 0)
+						s += strlen(mbox.name);
+					if(strstr(s, "body") != nil){
+						/* strip any known extensions */
+						for(i=0; exts[i].ext!=nil; i++){
+							j = strlen(exts[i].ext);
+							if(strlen(s)>j && strcmp(s+strlen(s)-j, exts[i].ext)==0){
+								s[strlen(s)-j] = '\0';
+								break;
+							}
+						}
+						if(strlen(s)>5 && strcmp(s+strlen(s)-5, "/body")==0)
+							s[strlen(s)-4] = '\0';	/* leave / in place */
+					}
+					nopen += mesgopen(&mbox, mbox.name, s, m, 0, nil);
+					while(*s!=0 && *s++!='\n')
+						;
+				}while(*s);
+				if(nopen == 0 && e->c1 == 'L')
+					nopen += replytoaddr(w, m, e, os);
+				if(nopen == 0)
+					winwriteevent(w, e);
+				free(buf);
+				break;
+
+			case 'I':	/* modify away; we don't care */
+			case 'D':
+				mesgtagpost(m);
+				/* fall through */
+			case 'd':
+			case 'i':
+				break;
+
+			default:
+				goto Unk;
+			}
+		}
+	}
+}
+
+void
+mesgline(Message *m, char *header, char *value)
+{
+	if(strlen(value) > 0)
+		Bprint(m->w->body, "%s: %s\n", header, value);
+}
+
+int
+isprintable(char *type)
+{
+	int i;
+
+	for(i=0; goodtypes[i]!=nil; i++)
+		if(strcmp(type, goodtypes[i])==0)
+			return 1;
+	return 0;
+}
+
+char*
+ext(char *type)
+{
+	int i;
+
+	for(i=0; exts[i].type!=nil; i++)
+		if(strcmp(type, exts[i].type)==0)
+			return exts[i].ext;
+	return "";
+}
+
+void
+mimedisplay(Message *m, char *name, char *rootdir, Window *w, int fileonly)
+{
+	char *dest;
+
+	if(strcmp(m->disposition, "file")==0 || strlen(m->filename)!=0){
+		if(strlen(m->filename) == 0){
+			dest = estrdup(m->name);
+			dest[strlen(dest)-1] = '\0';
+		}else
+			dest = estrdup(m->filename);
+		if(m->filename[0] != '/')
+			dest = egrow(estrdup(home), "/", dest);
+		Bprint(w->body, "\tcp %s%sbody%s %q\n", rootdir, name, ext(m->type), dest);
+		free(dest);
+	}else if(!fileonly)
+		Bprint(w->body, "\tfile is %s%sbody%s\n", rootdir, name, ext(m->type));
+}
+
+void
+printheader(char *dir, Biobuf *b)
+{
+	char *s;
+	char *lines[100];
+	int i, j, n;
+
+	s = readfile(dir, "header", nil);
+	if(s == nil)
+		return;
+	n = getfields(s, lines, nelem(lines), 0, "\n");
+	for(i=0; i<n; i++)
+		for(j=0; okheaders[j]; j++)
+			if(cistrncmp(lines[i], okheaders[j], strlen(okheaders[j])) == 0)
+				Bprint(b, "%s\n", lines[i]);
+	free(s);
+}
+
+void
+mesgload(Message *m, char *rootdir, char *file, Window *w)
+{
+	char *s, *subdir, *name, *dir;
+	Message *mp, *thisone;
+	int n;
+
+	dir = estrstrdup(rootdir, file);
+
+	if(strcmp(m->type, "message/rfc822") != 0){	/* suppress headers of envelopes */
+		if(strlen(m->from) > 0){
+			Bprint(w->body, "From: %s\n", m->from);
+			mesgline(m, "Date", m->date);
+			mesgline(m, "To", m->to);
+			mesgline(m, "CC", m->cc);
+			mesgline(m, "Subject", m->subject);
+		}else
+			printheader(dir, w->body);
+		Bprint(w->body, "\n");
+	}
+
+	if(m->level == 1 && m->recursed == 0){
+		m->recursed = 1;
+		readmbox(m, rootdir, m->name);
+	}
+	if(m->head == nil){	/* single part message */
+		if(strcmp(m->type, "text")==0 || strncmp(m->type, "text/", 5)==0){
+			mimedisplay(m, m->name, rootdir, w, 1);
+			s = readbody(m->type, dir, &n);
+			winwritebody(w, s, n);
+			free(s);
+		}else
+			mimedisplay(m, m->name, rootdir, w, 0);
+	}else{
+		/* multi-part message, either multipart/* or message/rfc822 */
+		thisone = nil;
+		if(strcmp(m->type, "multipart/alternative") == 0){
+			thisone = m->head;	/* in case we can't find a good one */
+			for(mp=m->head; mp!=nil; mp=mp->next)
+				if(isprintable(mp->type)){
+					thisone = mp;
+					break;
+				}
+		}
+		for(mp=m->head; mp!=nil; mp=mp->next){
+			if(thisone!=nil && mp!=thisone)
+				continue;
+			subdir = estrstrdup(dir, mp->name);
+			name = estrstrdup(file, mp->name);
+			/* skip first element in name because it's already in window name */
+			if(mp != m->head)
+				Bprint(w->body, "\n===> %s (%s) [%s]\n", strchr(name, '/')+1, mp->type, mp->disposition);
+			if(strcmp(mp->type, "text")==0 || strncmp(mp->type, "text/", 5)==0){
+				mimedisplay(mp, name, rootdir, w, 1);
+				printheader(subdir, w->body);
+				winwritebody(w, "\n", 1);
+				s = readbody(mp->type, subdir, &n);
+				winwritebody(w, s, n);
+				free(s);
+			}else{
+				if(strncmp(mp->type, "multipart/", 10)==0 || strcmp(mp->type, "message/rfc822")==0){
+					mp->w = w;
+					mesgload(mp, rootdir, name, w);
+					mp->w = nil;
+				}else
+					mimedisplay(mp, name, rootdir, w, 0);
+			}
+			free(name);
+			free(subdir);
+		}
+	}
+	free(dir);
+}
+
+int
+tokenizec(char *str, char **args, int max, char *splitc)
+{
+	int na;
+	int intok = 0;
+
+	if(max <= 0)
+		return 0;	
+	for(na=0; *str != '\0';str++){
+		if(strchr(splitc, *str) == nil){
+			if(intok)
+				continue;
+			args[na++] = str;
+			intok = 1;
+		}else{
+			/* it's a separator/skip character */
+			*str = '\0';
+			if(intok){
+				intok = 0;
+				if(na >= max)
+					break;
+			}
+		}
+	}
+	return na;
+}
+
+Message*
+mesglookup(Message *mbox, char *name, char *digest)
+{
+	int n;
+	Message *m;
+	char *t;
+
+	if(digest){
+		/* can find exactly */
+		for(m=mbox->head; m!=nil; m=m->next)
+			if(strcmp(digest, m->digest) == 0)
+				break;
+		return m;
+	}
+
+	n = strlen(name);
+	if(n == 0)
+		return nil;
+	if(name[n-1] == '/')
+		t = estrdup(name);
+	else
+		t = estrstrdup(name, "/");
+	for(m=mbox->head; m!=nil; m=m->next)
+		if(strcmp(t, m->name) == 0)
+			break;
+	free(t);
+	return m;
+}
+
+/*
+ * Find plumb port, knowing type is text, given file name (by extension)
+ */
+int
+plumbportbysuffix(char *file)
+{
+	char *suf;
+	int i, nsuf, nfile;
+
+	nfile = strlen(file);
+	for(i=0; ports[i].type!=nil; i++){
+		suf = ports[i].suffix;
+		nsuf = strlen(suf);
+		if(nfile > nsuf)
+			if(cistrncmp(file+nfile-nsuf, suf, nsuf) == 0)
+				return i;
+	}
+	return 0;
+}
+
+/*
+ * Find plumb port using type and file name (by extension)
+ */
+int
+plumbport(char *type, char *file)
+{
+	int i;
+
+	for(i=0; ports[i].type!=nil; i++)
+		if(strncmp(type, ports[i].type, strlen(ports[i].type)) == 0)
+			return i;
+	/* see if it's a text type */
+	for(i=0; goodtypes[i]!=nil; i++)
+		if(strncmp(type, goodtypes[i], strlen(goodtypes[i])) == 0)
+			return plumbportbysuffix(file);
+	return -1;
+}
+
+void
+plumb(Message *m, char *dir)
+{
+	int i;
+	char *port;
+	Plumbmsg *pm;
+
+	if(strlen(m->type) == 0)
+		return;
+	i = plumbport(m->type, m->filename);
+	if(i < 0)
+		fprint(2, "can't find destination for message subpart\n");
+	else{
+		port = ports[i].port;
+		pm = emalloc(sizeof(Plumbmsg));
+		pm->src = estrdup("Mail");
+		if(port)
+			pm->dst = estrdup(port);
+		else
+			pm->dst = nil;
+		pm->wdir = nil;
+		pm->type = estrdup("text");
+		pm->ndata = -1;
+		pm->data = estrstrdup(dir, "body");
+		pm->data = eappend(pm->data, "", ports[i].suffix);
+		if(plumbsend(plumbsendfd, pm) < 0)
+			fprint(2, "error writing plumb message: %r\n");
+		plumbfree(pm);
+	}
+}
+
+int
+mesgopen(Message *mbox, char *dir, char *s, Message *mesg, int plumbed, char *digest)
+{
+	char *t, *u, *v;
+	Message *m;
+	char *direlem[10];
+	int i, ndirelem, reuse;
+
+	/* find white-space-delimited first word */
+	for(t=s; *t!='\0' && !isspace(*t); t++)
+		;
+	u = emalloc(t-s+1);
+	memmove(u, s, t-s);
+	/* separate it on slashes */
+	ndirelem = tokenizec(u, direlem, nelem(direlem), "/");
+	if(ndirelem <= 0){
+    Error:
+		free(u);
+		return 0;
+	}
+	if(plumbed){
+		write(wctlfd, "top", 3);
+		write(wctlfd, "current", 7);
+	}
+	/* open window for message */
+	m = mesglookup(mbox, direlem[0], digest);
+	if(m == nil)
+		goto Error;
+	if(mesg!=nil && m!=mesg)	/* string looked like subpart but isn't part of this message */
+		goto Error;
+	if(m->opened == 0){
+		if(m->w == nil){
+			reuse = 0;
+			m->w = newwindow();
+		}else{
+			reuse = 1;
+			/* re-use existing window */
+			if(winsetaddr(m->w, "0,$", 1)){
+				if(m->w->data < 0)
+					m->w->data = winopenfile(m->w, "data");
+				write(m->w->data, "", 0);
+			}
+		}
+		v = estrstrdup(mbox->name, m->name);
+		winname(m->w, v);
+		free(v);
+		if(!reuse){
+			if(m->deleted)
+				wintagwrite(m->w, "Q Reply all UnDelmesg Save ", 2+6+4+10+5);
+			else
+				wintagwrite(m->w, "Q Reply all Delmesg Save ", 2+6+4+8+5);
+		}
+		threadcreate(mesgctl, m, STACK);
+		winopenbody(m->w, OWRITE);
+		mesgload(m, dir, m->name, m->w);
+		winclosebody(m->w);
+		winclean(m->w);
+		m->opened = 1;
+		if(ndirelem == 1){
+			free(u);
+			return 1;
+		}
+	}
+	if(ndirelem == 1 && plumbport(m->type, m->filename) <= 0){
+		/* make sure dot is visible */
+		ctlprint(m->w->ctl, "show\n");
+		return 0;
+	}
+	/* walk to subpart */
+	dir = estrstrdup(dir, m->name);
+	for(i=1; i<ndirelem; i++){
+		m = mesglookup(m, direlem[i], digest);
+		if(m == nil)
+			break;
+		dir = egrow(dir, m->name, nil);
+	}
+	if(m != nil && plumbport(m->type, m->filename) > 0)
+		plumb(m, dir);
+	free(dir);
+	free(u);
+	return 1;
+}
+
+void
+rewritembox(Window *w, Message *mbox)
+{
+	Message *m, *next;
+	char *deletestr, *t;
+	int nopen;
+
+	deletestr = estrstrdup("delete ", fsname);
+
+	nopen = 0;
+	for(m=mbox->head; m!=nil; m=next){
+		next = m->next;
+		if(m->deleted == 0)
+			continue;
+		if(m->opened){
+			nopen++;
+			continue;
+		}
+		if(m->writebackdel){
+			/* messages deleted by plumb message are not removed again */
+			t = estrdup(m->name);
+			if(strlen(t) > 0)
+				t[strlen(t)-1] = '\0';
+			deletestr = egrow(deletestr, " ", t);
+		}
+		mesgmenudel(w, mbox, m);
+		mesgdel(mbox, m);
+	}
+	if(write(mbox->ctlfd, deletestr, strlen(deletestr)) < 0)
+		fprint(2, "Mail: warning: error removing mail message files: %r\n");
+	free(deletestr);
+	winselect(w, "0", 0);
+	if(nopen == 0)
+		winclean(w);
+	mbox->dirty = 0;
+}
+
+/* name is a full file name, but it might not belong to us */
+Message*
+mesglookupfile(Message *mbox, char *name, char *digest)
+{
+	int k, n;
+
+	k = strlen(name);
+	n = strlen(mbox->name);
+	if(k==0 || strncmp(name, mbox->name, n) != 0){
+//		fprint(2, "Mail: message %s not in this mailbox\n", name);
+		return nil;
+	}
+	return mesglookup(mbox, name+n, digest);
+}

+ 31 - 0
acme/mail/src/mkfile

@@ -0,0 +1,31 @@
+</$objtype/mkfile
+
+TARG=Mail
+OFILES=\
+		html.$O\
+		mail.$O\
+		mesg.$O\
+		reply.$O\
+		util.$O\
+		win.$O
+
+HFILES=dat.h
+LIB=
+
+BIN=../$objtype
+
+UPDATE=\
+	mkfile\
+	$HFILES\
+	${OFILES:%.$O=%.c}\
+	${TARG:%=/acme/mail/386/%}\
+
+</sys/src/cmd/mkone
+
+$O.out: $OFILES
+	$LD -o $target  $LDFLAGS $OFILES
+
+syms:V:
+	8c -a mail.c	>syms
+	8c -aa mesg.c reply.c util.c win.c 	>>syms
+

+ 562 - 0
acme/mail/src/reply.c

@@ -0,0 +1,562 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <ctype.h>
+#include <plumb.h>
+#include "dat.h"
+
+static int	replyid;
+
+int
+quote(Message *m, Biobuf *b, char *dir, char *quotetext)
+{
+	char *body, *type;
+	int i, n, nlines;
+	char **lines;
+
+	if(quotetext){
+		body = quotetext;
+		n = strlen(body);
+		type = nil;
+	}else{
+		/* look for first textual component to quote */
+		type = readfile(dir, "type", &n);
+		if(type == nil){
+			print("no type in %s\n", dir);
+			return 0;
+		}
+		if(strncmp(type, "multipart/", 10)==0 || strncmp(type, "message/", 8)==0){
+			dir = estrstrdup(dir, "1/");
+			if(quote(m, b, dir, nil)){
+				free(type);
+				free(dir);
+				return 1;
+			}
+			free(dir);
+		}
+		if(strncmp(type, "text", 4) != 0){
+			free(type);
+			return 0;
+		}
+		body = readbody(m->type, dir, &n);
+		if(body == nil)
+			return 0;
+	}
+	nlines = 0;
+	for(i=0; i<n; i++)
+		if(body[i] == '\n')
+			nlines++;
+	nlines++;
+	lines = emalloc(nlines*sizeof(char*));
+	nlines = getfields(body, lines, nlines, 0, "\n");
+	/* delete leading and trailing blank lines */
+	i = 0;
+	while(i<nlines && lines[i][0]=='\0')
+		i++;
+	while(i<nlines && lines[nlines-1][0]=='\0')
+		nlines--;
+	while(i < nlines){
+		Bprint(b, ">%s%s\n", lines[i][0]=='>'? "" : " ", lines[i]);
+		i++;
+	}
+	free(lines);
+	free(body);	/* will free quotetext if non-nil */
+	free(type);
+	return 1;
+}
+
+void
+mkreply(Message *m, char *label, char *to, Plumbattr *attr, char *quotetext)
+{
+	Message *r;
+	char *dir, *t;
+	int quotereply;
+	Plumbattr *a;
+
+	quotereply = (label[0] == 'Q');
+	r = emalloc(sizeof(Message));
+	r->isreply = 1;
+	if(m != nil)
+		r->replyname = estrdup(m->name);
+	r->next = replies.head;
+	r->prev = nil;
+	if(replies.head != nil)
+		replies.head->prev = r;
+	replies.head = r;
+	if(replies.tail == nil)
+		replies.tail = r;
+	r->name = emalloc(strlen(mbox.name)+strlen(label)+10);
+	sprint(r->name, "%s%s%d", mbox.name, label, ++replyid);
+	r->w = newwindow();
+	winname(r->w, r->name);
+	wintagwrite(r->w, "|fmt Post", 5+4);
+	r->tagposted = 1;
+	threadcreate(mesgctl, r, STACK);
+	winopenbody(r->w, OWRITE);
+	if(to!=nil && to[0]!='\0')
+		Bprint(r->w->body, "%s\n", to);
+	for(a=attr; a; a=a->next)
+		Bprint(r->w->body, "%s: %s\n", a->name, a->value);
+	dir = nil;
+	if(m != nil){
+		dir = estrstrdup(mbox.name, m->name);
+		if(to == nil && attr == nil){
+			/* Reply goes to replyto; Reply all goes to From and To and CC */
+			if(strstr(label, "all") == nil)
+				Bprint(r->w->body, "To: %s\n", m->replyto);
+			else{	/* Replyall */
+				if(strlen(m->from) > 0)
+					Bprint(r->w->body, "To: %s\n", m->from);
+				if(strlen(m->to) > 0)
+					Bprint(r->w->body, "To: %s\n", m->to);
+				if(strlen(m->cc) > 0)
+					Bprint(r->w->body, "CC: %s\n", m->cc);
+			}
+		}
+		if(strlen(m->subject) > 0){
+			t = "Subject: Re: ";
+			if(strlen(m->subject) >= 3)
+				if(tolower(m->subject[0])=='r' && tolower(m->subject[1])=='e' && m->subject[2]==':')
+					t = "Subject: ";
+			Bprint(r->w->body, "%s%s\n", t, m->subject);
+		}
+		if(!quotereply){
+			Bprint(r->w->body, "Include: %sraw\n", dir);
+			free(dir);
+		}
+	}
+	Bprint(r->w->body, "\n");
+	if(m == nil)
+		Bprint(r->w->body, "\n");
+	else if(quotereply){
+		quote(m, r->w->body, dir, quotetext);
+		free(dir);
+	}
+	winclosebody(r->w);
+	if(m==nil && (to==nil || to[0]=='\0'))
+		winselect(r->w, "0", 0);
+	else
+		winselect(r->w, "$", 0);
+	winclean(r->w);
+	windormant(r->w);
+}
+
+void
+delreply(Message *m)
+{
+	if(m->next == nil)
+		replies.tail = m->prev;
+	else
+		m->next->prev = m->prev;
+	if(m->prev == nil)
+		replies.head = m->next;
+	else
+		m->prev->next = m->next;
+	mesgfreeparts(m);
+	free(m);
+}
+
+/* copy argv to stack and free the incoming strings, so we don't leak argument vectors */
+void
+buildargv(char **inargv, char *argv[NARGS+1], char args[NARGCHAR])
+{
+	int i, n;
+	char *s, *a;
+
+	s = args;
+	for(i=0; i<NARGS; i++){
+		a = inargv[i];
+		if(a == nil)
+			break;
+		n = strlen(a)+1;
+		if((s-args)+n >= NARGCHAR)	/* too many characters */
+			break;
+		argv[i] = s;
+		memmove(s, a, n);
+		s += n;
+		free(a);
+	}
+	argv[i] = nil;
+}
+
+void
+execproc(void *v)
+{
+	struct Exec *e;
+	int p[2], q[2];
+	char *prog;
+	char *argv[NARGS+1], args[NARGCHAR];
+
+	e = v;
+	p[0] = e->p[0];
+	p[1] = e->p[1];
+	q[0] = e->q[0];
+	q[1] = e->q[1];
+	prog = e->prog;	/* known not to be malloc'ed */
+	rfork(RFFDG);
+	sendul(e->sync, 1);
+	buildargv(e->argv, argv, args);
+	free(e->argv);
+	chanfree(e->sync);
+	free(e);
+	dup(p[0], 0);
+	close(p[0]);
+	close(p[1]);
+	if(q[0]){
+		dup(q[1], 1);
+		close(q[0]);
+		close(q[1]);
+	}
+	procexec(nil, prog, argv);
+//fprint(2, "exec: %s", e->prog);
+//{int i;
+//for(i=0; argv[i]; i++) print(" '%s'", argv[i]);
+//print("\n");
+//}
+//argv[0] = "cat";
+//argv[1] = nil;
+//procexec(nil, "/bin/cat", argv);
+	fprint(2, "Mail: can't exec %s: %r\n", prog);
+	threadexits("can't exec");
+}
+
+enum{
+	ATTACH,
+	BCC,
+	CC,
+	FROM,
+	INCLUDE,
+	TO,
+};
+
+char *headers[] = {
+	"attach:",
+	"bcc:",
+	"cc:",
+	"from:",
+	"include:",
+	"to:",
+	nil,
+};
+
+int
+whichheader(char *h)
+{
+	int i;
+
+	for(i=0; headers[i]!=nil; i++)
+		if(cistrcmp(h, headers[i]) == 0)
+			return i;
+	return -1;
+}
+
+char *tolist[200];
+char	*cclist[200];
+char	*bcclist[200];
+int ncc, nbcc, nto;
+char	*attlist[200];
+char	included[200];
+
+int
+addressed(char *name)
+{
+	int i;
+
+	for(i=0; i<nto; i++)
+		if(strcmp(name, tolist[i]) == 0)
+			return 1;
+	for(i=0; i<ncc; i++)
+		if(strcmp(name, cclist[i]) == 0)
+			return 1;
+	for(i=0; i<nbcc; i++)
+		if(strcmp(name, bcclist[i]) == 0)
+			return 1;
+	return 0;
+}
+
+char*
+skipbl(char *s, char *e)
+{
+	while(s < e){
+		if(*s!=' ' && *s!='\t' && *s!=',')
+			break;
+		s++;
+	}
+	return s;
+}
+
+char*
+findbl(char *s, char *e)
+{
+	while(s < e){
+		if(*s==' ' || *s=='\t' || *s==',')
+			break;
+		s++;
+	}
+	return s;
+}
+
+/*
+ * comma-separate possibly blank-separated strings in line; e points before newline
+ */
+void
+commas(char *s, char *e)
+{
+	char *t;
+
+	/* may have initial blanks */
+	s = skipbl(s, e);
+	while(s < e){
+		s = findbl(s, e);
+		if(s == e)
+			break;
+		t = skipbl(s, e);
+		if(t == e)	/* no more words */
+			break;
+		/* patch comma */
+		*s++ = ',';
+		while(s < t)
+			*s++ = ' ';
+	}
+}
+
+int
+print2(int fd, int ofd, char *fmt, ...)
+{
+	int m, n;
+	char *s;
+	va_list arg;
+
+	va_start(arg, fmt);
+	s = vsmprint(fmt, arg);
+	va_end(arg);
+	if(s == nil)
+		return -1;
+	m = strlen(s);
+	n = write(fd, s, m);
+	if(ofd > 0)
+		write(ofd, s, m);
+	return n;
+}
+
+void
+write2(int fd, int ofd, char *buf, int n, int nofrom)
+{
+	char *from, *p;
+	int m;
+
+	write(fd, buf, n);
+
+	if(ofd <= 0)
+		return;
+
+	if(nofrom == 0){
+		write(ofd, buf, n);
+		return;
+	}
+
+	/* need to escape leading From lines to avoid corrupting 'outgoing' mailbox */
+	for(p=buf; *p; p+=m){
+		from = cistrstr(p, "from");
+		if(from == nil)
+			m = n;
+		else
+			m = from - p;
+		if(m > 0)
+			write(ofd, p, m);
+		if(from){
+			if(p==buf || from[-1]=='\n')
+				write(ofd, " ", 1);	/* escape with space if From is at start of line */
+			write(ofd, from, 4);
+			m += 4;
+		}
+		n -= m;
+	}
+}
+
+void
+mesgsend(Message *m)
+{
+	char *s, *body, *to;
+	int i, j, h, n, natt, p[2];
+	struct Exec *e;
+	Channel *sync;
+	int first, nfld, delit, ofd;
+	char *copy, *fld[100], *now;
+
+	body = winreadbody(m->w, &n);
+	/* assemble to: list from first line, to: line, and cc: line */
+	nto = 0;
+	natt = 0;
+	ncc = 0;
+	nbcc = 0;
+	first = 1;
+	to = body;
+	for(;;){
+		for(s=to; *s!='\n'; s++)
+			if(*s == '\0'){
+				free(body);
+				return;
+			}
+		if(s++ == to)	/* blank line */
+			break;
+		/* make copy of line to tokenize */
+		copy = emalloc(s-to);
+		memmove(copy, to, s-to);
+		copy[s-to-1] = '\0';
+		nfld = tokenizec(copy, fld, nelem(fld), ", \t");
+		if(nfld == 0){
+			free(copy);
+			break;
+		}
+		n -= s-to;
+		switch(h = whichheader(fld[0])){
+		case TO:
+		case FROM:
+			delit = 1;
+			commas(to+strlen(fld[0]), s-1);
+			for(i=1; i<nfld && nto<nelem(tolist); i++)
+				if(!addressed(fld[i]))
+					tolist[nto++] = estrdup(fld[i]);
+			break;
+		case BCC:
+			delit = 1;
+			commas(to+strlen(fld[0]), s-1);
+			for(i=1; i<nfld && nbcc<nelem(bcclist); i++)
+				if(!addressed(fld[i]))
+					bcclist[nbcc++] = estrdup(fld[i]);
+			break;
+		case CC:
+			delit = 1;
+			commas(to+strlen(fld[0]), s-1);
+			for(i=1; i<nfld && ncc<nelem(cclist); i++)
+				if(!addressed(fld[i]))
+					cclist[ncc++] = estrdup(fld[i]);
+			break;
+		case ATTACH:
+		case INCLUDE:
+			delit = 1;
+			for(i=1; i<nfld && natt<nelem(attlist); i++){
+				attlist[natt] = estrdup(fld[i]);
+				included[natt++] = (h == INCLUDE);
+			}
+			break;
+		default:
+			if(first){
+				delit = 1;
+				for(i=0; i<nfld && nto<nelem(tolist); i++)
+					tolist[nto++] = estrdup(fld[i]);
+			}else	/* ignore it */
+				delit = 0;
+			break;
+		}
+		if(delit){
+			/* delete line from body */
+			memmove(to, s, n+1);
+		}else
+			to = s;
+		free(copy);
+		first = 0;
+	}
+
+	ofd = open(outgoing, OWRITE|OCEXEC);	/* no error check necessary */
+	if(ofd > 0){
+		/* From dhog Fri Aug 24 22:13:00 EDT 2001 */
+		now = ctime(time(0));
+		fprint(ofd, "From %s %s", user, now);
+		fprint(ofd, "From: %s\n", user);
+		fprint(ofd, "Date: %s", now);
+		for(i=0; i<natt; i++)
+			if(included[i])
+				fprint(ofd, "Include: %s\n", attlist[i]);
+			else
+				fprint(ofd, "Attach: %s\n", attlist[i]);
+		/* needed because mail is by default Latin-1 */
+		fprint(ofd, "Content-Type: text/plain; charset=\"UTF-8\"\n");
+		fprint(ofd, "Content-Transfer-Encoding: 8bit\n");
+	}
+
+	e = emalloc(sizeof(struct Exec));
+	if(pipe(p) < 0)
+		error("can't create pipe: %r");
+	e->p[0] = p[0];
+	e->p[1] = p[1];
+	e->prog = "/bin/upas/marshal";
+	e->argv = emalloc((1+1+4*natt+1)*sizeof(char*));
+	e->argv[0] = estrdup("marshal");
+	e->argv[1] = estrdup("-8");
+	j = 2;
+	for(i=0; i<natt; i++){
+		if(included[i])
+			e->argv[j++] = estrdup("-A");
+		else
+			e->argv[j++] = estrdup("-a");
+		e->argv[j++] = estrdup(attlist[i]);
+	}
+	sync = chancreate(sizeof(int), 0);
+	e->sync = sync;
+	proccreate(execproc, e, EXECSTACK);
+	recvul(sync);
+	close(p[0]);
+
+	/* using marshal -8, so generate rfc822 headers */
+	if(nto > 0){
+		print2(p[1], ofd, "To: ");
+		for(i=0; i<nto-1; i++)
+			print2(p[1], ofd, "%s, ", tolist[i]);
+		print2(p[1], ofd, "%s\n", tolist[i]);
+	}
+	if(ncc > 0){
+		print2(p[1], ofd, "CC: ");
+		for(i=0; i<ncc-1; i++)
+			print2(p[1], ofd, "%s, ", cclist[i]);
+		print2(p[1], ofd, "%s\n", cclist[i]);
+	}
+	if(nbcc > 0){
+		print2(p[1], ofd, "BCC: ");
+		for(i=0; i<nbcc-1; i++)
+			print2(p[1], ofd, "%s, ", bcclist[i]);
+		print2(p[1], ofd, "%s\n", bcclist[i]);
+	}
+
+	i = strlen(body);
+	if(i > 0)
+		write2(p[1], ofd, body, i, 1);
+
+	/* guarantee a blank line, to ensure attachments are separated from body */
+	if(i==0 || body[i-1]!='\n')
+		write2(p[1], ofd, "\n\n", 2, 0);
+	else if(i>1 && body[i-2]!='\n')
+		write2(p[1], ofd, "\n", 1, 0);
+
+	/* these look like pseudo-attachments in the "outgoing" box */
+	if(ofd>0 && natt>0){
+		for(i=0; i<natt; i++)
+			if(included[i])
+				fprint(ofd, "=====> Include: %s\n", attlist[i]);
+			else
+				fprint(ofd, "=====> Attach: %s\n", attlist[i]);
+	}
+	if(ofd > 0)
+		write(ofd, "\n", 1);
+
+	for(i=0; i<natt; i++)
+		free(attlist[i]);
+	close(ofd);
+	close(p[1]);
+	free(body);
+
+	if(m->replyname != nil)
+		mesgmenumark(mbox.w, m->replyname, "\t[replied]");
+	if(m->name[0] == '/')
+		s = estrdup(m->name);
+	else
+		s = estrstrdup(mbox.name, m->name);
+	s = egrow(s, "-R", nil);
+	winname(m->w, s);
+	free(s);
+	winclean(m->w);
+	/* mark message unopened because it's no longer the original message */
+	m->opened = 0;
+}

Some files were not shown because too many files changed in this diff