diff --git a/acme/.dummy b/acme/.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/acme/acid/Acid b/acme/acid/Acid new file mode 100755 index 000000000..fa508caec --- /dev/null +++ b/acme/acid/Acid @@ -0,0 +1,6 @@ +#!/bin/rc +if(~ $#* 0){ + echo usage: Acid pid >[2=1] + exit usage +} +win acid -l acme $* diff --git a/acme/acid/guide b/acme/acid/guide new file mode 100644 index 000000000..185b20b08 --- /dev/null +++ b/acme/acid/guide @@ -0,0 +1,3 @@ +broke|rc kill program|rc +Acid pid +Acid -l thread -l acidfile pid diff --git a/386/bin/.dummy b/acme/bin/386/.dummy similarity index 100% rename from 386/bin/.dummy rename to acme/bin/386/.dummy diff --git a/acme/bin/Battery b/acme/bin/Battery new file mode 100755 index 000000000..679074c63 --- /dev/null +++ b/acme/bin/Battery @@ -0,0 +1,30 @@ +#!/bin/rc + +if(! test -f /mnt/apm/battery){ + echo no apm >[1=2] + exit 'no apm' +} + +cd /mnt/acme/new +echo name /dev/apm >ctl +echo dump Battery >ctl + +awkscript=' +NR==1 { + if($3 != -1) + printf("%d%% %d:%02d %s", $2, $3/3600, ($3/60)%60, $1); + else + printf("%d%% %s", $2, $1); +} +' + +fn chk { + what=`{awk $awkscript /mnt/apm/battery} + echo cleartag >ctl || exit die + echo clean >ctl || exit die + echo ' '^$"what >tag || exit die +} + +chk +while(sleep 60) + chk diff --git a/acme/bin/Isspam b/acme/bin/Isspam new file mode 100755 index 000000000..2c30d7703 --- /dev/null +++ b/acme/bin/Isspam @@ -0,0 +1,13 @@ +#!/bin/rc + +if(! ~ $#* 0){ + echo usage: Isspam >[1=2] + exit usage +} + +if(! ~ `{pwd} /mail/fs/*/[0-9]* || ! test -f raw || ! test -f unixheader){ + echo must run in mail directory >[1=2] + exit 'bad dir' +} + +cat unixheader raw | upas/isspam diff --git a/acme/bin/Mail b/acme/bin/Mail new file mode 100755 index 000000000..e1ae5729e --- /dev/null +++ b/acme/bin/Mail @@ -0,0 +1,9 @@ +#!/bin/rc + +#/mail/fs is read-protected unless fs is mounted +test -r /mail/fs || { + if(test -d /mnt/term/mail/fs/mbox) bind /mnt/term/mail/fs /mail/fs + if not upas/fs +} + +exec /acme/bin/$objtype/Mail $* diff --git a/acme/bin/Perl b/acme/bin/Perl new file mode 100755 index 000000000..c3544bcb6 --- /dev/null +++ b/acme/bin/Perl @@ -0,0 +1,7 @@ +#!/bin/rc + +# aperl: +# Executes perl command and alters stderr to produce Acme-friendly error messages +# Created 02-JUL-1996, Luther Huffman, lutherh@stratcom.com + +/bin/perl $* |[2] /bin/perl -pe 's/ line (\d+)/:$1 /' >[1=2] diff --git a/acme/bin/README b/acme/bin/README new file mode 100644 index 000000000..97667dc13 --- /dev/null +++ b/acme/bin/README @@ -0,0 +1,3 @@ +The source directory should be called ./src instead of ./source, +but this directory is bound into /bin and there is a command called +src that the local directory would hide. diff --git a/acme/bin/Spam b/acme/bin/Spam new file mode 100755 index 000000000..d423b8ec2 --- /dev/null +++ b/acme/bin/Spam @@ -0,0 +1,13 @@ +#!/bin/rc + +if(! ~ $#* 0){ + echo usage: Spam >[1=2] + exit usage +} + +if(! ~ `{pwd} /mail/fs/*/[0-9]* || ! test -f raw || ! test -f unixheader){ + echo must run in mail directory >[1=2] + exit 'bad dir' +} + +cat unixheader raw | upas/spam diff --git a/acme/bin/Unspam b/acme/bin/Unspam new file mode 100755 index 000000000..b68d2c7c0 --- /dev/null +++ b/acme/bin/Unspam @@ -0,0 +1,13 @@ +#!/bin/rc + +if(! ~ $#* 0){ + echo usage: Unspam >[1=2] + exit usage +} + +if(! ~ `{pwd} /mail/fs/*/[0-9]* || ! test -f raw || ! test -f unixheader){ + echo must run in mail directory >[1=2] + exit 'bad dir' +} + +cat unixheader raw | upas/unspam diff --git a/acme/bin/adiff b/acme/bin/adiff new file mode 100755 index 000000000..3d3d188f5 --- /dev/null +++ b/acme/bin/adiff @@ -0,0 +1,24 @@ +#!/bin/rc + +if(~ $#* 0 1){ + echo >[1=2] usage: adiff file1 file2 + echo >[1=2] or adiff file1 file2... dir + exit usage +} + +dir = /mnt/wsys +if(! test -f $dir/cons) + dir = /mnt/term/$dir +id=`{cat $dir/new/ctl} +id=$id(1) + +l=$1 +r=$2 +if (test -d $1) l=$1/`{basename $2} +if not if (test -d $2) r=$2/`{basename $1} + +echo 'name '^`{pwd}^/-diff-^`{basename $l} > $dir/$id/ctl + +diff $* | awk -v 'l='$l -v 'r='^$r '/^diff/ {l=$2; r=$3; next} /^[1-9]/ {sub("[acd]", " & " r ":"); sub("^", l ":", $0)} + {print $0}' > $dir/$id/body +echo clean > $dir/$id/ctl diff --git a/acme/bin/agrep b/acme/bin/agrep new file mode 100755 index 000000000..7d1234477 --- /dev/null +++ b/acme/bin/agrep @@ -0,0 +1,3 @@ +#!/bin/rc + +exec grep -n $* /dev/null diff --git a/386/bin/ip/.dummy b/acme/bin/alpha/.dummy similarity index 100% rename from 386/bin/ip/.dummy rename to acme/bin/alpha/.dummy diff --git a/acme/bin/ap b/acme/bin/ap new file mode 100755 index 000000000..d84fe0f33 --- /dev/null +++ b/acme/bin/ap @@ -0,0 +1,12 @@ +#!/bin/rc +args='' +while(~ $1 -*) { + args=$args^' '^$1 + shift 1 +} +if (~ $#1 0) + sysname=alice +if not + sysname=$1 +if (! test -f /n/$sysname/usr/spool/ap ) { 9fs $sysname } +eval exec /acme/bin/$cputype/apread $args $sysname diff --git a/386/lib/.dummy b/acme/bin/arm/.dummy similarity index 100% rename from 386/lib/.dummy rename to acme/bin/arm/.dummy diff --git a/acme/bin/aspell b/acme/bin/aspell new file mode 100755 index 000000000..7adbd148f --- /dev/null +++ b/acme/bin/aspell @@ -0,0 +1,43 @@ +#!/bin/rc + +spellflags=() +fflag='' +for(x){ + switch($x){ + case -[bcvx] + spellflags=($spellflags $x) + case -f + fflag=$x + case * + if(~ $fflag -f) { + spellflags=($spellflags -f $x) + fflag='' + } + if not args = ($args $x) + } +} + +dir = /mnt/wsys +if(! test -f $dir/cons) + dir = /mnt/term/$dir +id=`{cat $dir/new/ctl} +id=$id(1) + +if(~ $#args 1 && ~ $args /*){ + adir = `{basename -d $args} + args = `{basename $args} + echo 'name '^$adir^/-spell > $dir/$id/ctl + cd $adir +} +if not { + echo 'name '^`{pwd}^/-spell > $dir/$id/ctl +} + +{ + echo noscroll + if(~ $#args 0) + /acme/bin/$cputype/spout | sort -t: -u +2 | sort -t: +1.1n | aux/sprog -a $spellflags > $dir/$id/body + if not for(i in $args) + /acme/bin/$cputype/spout $i | sort -t: -u +2 | sort -t: +1.1n | aux/sprog -a $spellflags > $dir/$id/body + echo clean +}> $dir/$id/ctl diff --git a/68000/bin/.dummy b/acme/bin/dial/.dummy similarity index 100% rename from 68000/bin/.dummy rename to acme/bin/dial/.dummy diff --git a/acme/bin/guide b/acme/bin/guide new file mode 100644 index 000000000..eb54a633c --- /dev/null +++ b/acme/bin/guide @@ -0,0 +1,4 @@ +win +aspell file +adiff file1 file2 +adict -d oed diff --git a/acme/bin/ind b/acme/bin/ind new file mode 100755 index 000000000..b87130651 --- /dev/null +++ b/acme/bin/ind @@ -0,0 +1,3 @@ +#!/bin/rc + +sed 's/^/ /' $* diff --git a/68000/bin/ip/.dummy b/acme/bin/mips/.dummy similarity index 100% rename from 68000/bin/ip/.dummy rename to acme/bin/mips/.dummy diff --git a/acme/bin/new b/acme/bin/new new file mode 100755 index 000000000..52990d096 --- /dev/null +++ b/acme/bin/new @@ -0,0 +1,10 @@ +#!/bin/rc + +id=`{cat /mnt/acme/new/ctl} +id=$id(1) +cmd = $* +if(~ $#cmd 0) cmd = cat + +echo 'name '^`{pwd}^/-^`{basename $cmd(1)} > /mnt/acme/$id/ctl +$cmd > /mnt/acme/$id/body +echo clean > /mnt/acme/$id/ctl diff --git a/68000/lib/.dummy b/acme/bin/power/.dummy similarity index 100% rename from 68000/lib/.dummy rename to acme/bin/power/.dummy diff --git a/68020/bin/.dummy b/acme/bin/power64/.dummy similarity index 100% rename from 68020/bin/.dummy rename to acme/bin/power64/.dummy diff --git a/acme/bin/quote b/acme/bin/quote new file mode 100755 index 000000000..5c85df7c2 --- /dev/null +++ b/acme/bin/quote @@ -0,0 +1,3 @@ +#!/bin/rc + +sed 's/^/> /' $* diff --git a/acme/bin/source/acd/README b/acme/bin/source/acd/README new file mode 100644 index 000000000..4fba5b0c5 --- /dev/null +++ b/acme/bin/source/acd/README @@ -0,0 +1,33 @@ +This is a CD player for use under Acme. + +It is derived from my earlier cdplay, which +was in turn derived from a 2nd edition player +called vcd. I think hardly any of the code from +vcd is left anymore, but it's what got me started. +Vcd was originally by David Hogan with additions +by Alberto Nava. David Hogan claims the only +code left is the definition of struct Msf. + +Run it by executing "acd /dev/sdD0", where +/dev/sdD0 is your CD reader. + +A window with a track list will appear, with +tracks named Track 1, Track 2, etc. +If it can be found in the freedb.org CD database, +real track names will replace the boring +ones before long. + +To start playing a track, right click the number. +A "> " marks the currently playing track. +When that track finishes, acd plays the track +on the next line. This means you can edit +the window as thought it were a play list. + +If the next line is "repeat", acd will start again +at the first song listed in the window. + +CD changes are handled gracefully. + +Russ Cox +9 August 2000 +rsc@plan9.bell-labs.com diff --git a/acme/bin/source/acd/access b/acme/bin/source/acd/access new file mode 100644 index 000000000..9a34399e0 --- /dev/null +++ b/acme/bin/source/acd/access @@ -0,0 +1,243 @@ +TWO FORMS OF ACCESS TO THE FREEDB +--------------------------------- + +In the following document we will refer to CDDB instead of freedb, since +from a technical point of view, freedb is a CDDB-Server as it uses the +CDDB-protocol. + +If you are interested in incorporating the use of freedb in your +software, there are two forms of access that you may consider. + +1. Local access + + In this mode your software simply attempts to open local files on + the computer to access the CDDB. + +2. Remote access + + 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 DISCID Howto. + +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. + + +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 http://freedb.freedb.org. +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 +database-format specification. + +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. + + +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 current list of public servers 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 CDDB-protocol documentation. + +The CDDB entry returned from the server via a "cddb read" command is in +the format described database-format specification. + +You may experiment with the freedb server by connecting to port 888 of +the server host via the "telnet" program, and then typing the cddb +protocol commands by hand. For example: + + telnet freedb.freedb.org 888 + +connects you to the freedb server at freedb.freedb.org. + +Some additional notes for accessing freedb over the Internet: + +Your application should always specify the highest documented protocol +level. The highest level currently supported is "3". Lower protocol +levels will work, but are only provided for compatibility with older +CDDB applications. If you do not use the highest available protocol +level, certain important features will not be available to your +application. + +Make sure to use the proper arguments with the "hello" command. The user +and hostname arguments should be that of the user's email address, not +some fixed hard-coded value. The application name and version should be +that of your application, not that of another existing application. + +We consider the use of the "cddb query" command mandatory for all CDDB +clients. It is not valid to issue a "cddb read" command without issuing +a prior "cddb query" and receiving a good response, as it may yield incorrect +results. In addition, it is clients should support close matches +(aka "fuzzy" matches, or response code 211). + +The proper way to handle multiple fuzzy matches is to present the +entire list of matches to the user and to let the user choose between them. +Matches are listed in the order of best fit for the user's disc, so they +should be presented to the user in the order they are listed by the server. + +The suggested algorithm for obtaining the list of server sites is +as follows. The application should offer to get the list from +freedb.freedb.org with the "sites" command the first time the user runs +the program. Additionally the application should provide the user with +some method of downloading the list on-demand. + +We do strongly suggest that you provide your users with the capability of +choosing freedb server sites as described above. However, for some +applications this may not be feasible. If you do not wish to offer this +functionality, you may safely hard-code "freedb.freedb.org" in your +application as the sole freedb site to access. This will deprive your users +of the option to choose a site near their locale for optimal response, but +that is your choice. diff --git a/acme/bin/source/acd/acd.h b/acme/bin/source/acd/acd.h new file mode 100644 index 000000000..90c4bdb81 --- /dev/null +++ b/acme/bin/source/acd/acd.h @@ -0,0 +1,171 @@ +#include +#include +#include +#include +#include +#include +#include +#include <9p.h> + +/* acme */ +typedef struct Event Event; +typedef struct Window Window; + +enum +{ + STACK = 16384, + EVENTSIZE = 256, + NEVENT = 5, +}; + +struct Event +{ + int c1; + int c2; + int q0; + int q1; + int flag; + int nb; + int nr; + char b[EVENTSIZE*UTFmax+1]; + Rune r[EVENTSIZE+1]; +}; + +struct Window +{ + /* file descriptors */ + int ctl; + int event; + int addr; + int data; + Biobuf *body; + + /* event input */ + char buf[512]; + char *bufp; + int nbuf; + Event e[NEVENT]; + + int id; + int open; + Channel *cevent; /* chan(Event*) */ +}; + +extern Window* newwindow(void); +extern int winopenfile(Window*, char*); +extern void winopenbody(Window*, int); +extern void winclosebody(Window*); +extern void wintagwrite(Window*, char*, int); +extern void winname(Window*, char*); +extern void winwriteevent(Window*, Event*); +extern void winread(Window*, uint, uint, char*); +extern int windel(Window*, int); +extern void wingetevent(Window*, Event*); +extern void wineventproc(void*); +extern void winwritebody(Window*, char*, int); +extern void winclean(Window*); +extern int winselect(Window*, char*, int); +extern int winsetaddr(Window*, char*, int); +extern char* winreadbody(Window*, int*); +extern void windormant(Window*); +extern void winsetdump(Window*, char*, char*); + +extern char* readfile(char*, char*, int*); +extern void ctlprint(int, char*, ...); +extern void* emalloc(uint); +extern char* estrdup(char*); +extern char* estrstrdup(char*, char*); +extern char* egrow(char*, char*, char*); +extern char* eappend(char*, char*, char*); +extern void error(char*, ...); +extern int tokenizec(char*, char**, int, char*); + +/* cd stuff */ +typedef struct Msf Msf; /* minute, second, frame */ +struct Msf { + int m; + int s; + int f; +}; + +typedef struct Track Track; +struct Track { + Msf start; + Msf end; + ulong bstart; + ulong bend; + char *title; +}; + +enum { + MTRACK = 64, +}; +typedef struct Toc Toc; +struct Toc { + int ntrack; + int nchange; + int changetime; + int track0; + Track track[MTRACK]; + char *title; +}; + +extern int msfconv(Fmt*); + +#pragma varargck argpos error 1 +#pragma varargck argpos ctlprint 2 +#pragma varargck type "M" Msf + +enum { /* state */ + Sunknown, + Splaying, + Spaused, + Scompleted, + Serror, +}; + +typedef struct Cdstatus Cdstatus; +struct Cdstatus { + int state; + int track; + int index; + Msf abs; + Msf rel; +}; + +typedef struct Drive Drive; +struct Drive { + Window *w; + Channel *cstatus; /* chan(Cdstatus) */ + Channel *ctocdisp; /* chan(Toc) */ + Channel *cdbreq; /* chan(Toc) */ + Channel *cdbreply; /* chan(Toc) */ + Scsi *scsi; + Toc toc; + Cdstatus status; +}; + +int gettoc(Scsi*, Toc*); +void drawtoc(Window*, Drive*, Toc*); +void redrawtoc(Window*, Toc*); +void tocproc(void*); /* Drive* */ +void cddbproc(void*); /* Drive* */ +void cdstatusproc(void*); /* Drive* */ + +extern int debug; + +#define DPRINT if(debug)fprint +void acmeevent(Drive*, Window*, Event*); + +int playtrack(Drive*, int, int); +int pause(Drive*); +int resume(Drive*); +int stop(Drive*); +int eject(Drive*); +int ingest(Drive*); + +int markplay(Window*, ulong); +int setplaytime(Window*, char*); +void advancetrack(Drive*, Window*); + + diff --git a/acme/bin/source/acd/acme.c b/acme/bin/source/acd/acme.c new file mode 100644 index 000000000..514bdbc6e --- /dev/null +++ b/acme/bin/source/acd/acme.c @@ -0,0 +1,347 @@ +#include "acd.h" + +static int +iscmd(char *s, char *cmd) +{ + int len; + + len = strlen(cmd); + return strncmp(s, cmd, len)==0 && (s[len]=='\0' || s[len]==' ' || s[len]=='\t' || s[len]=='\n'); +} + +static char* +skip(char *s, char *cmd) +{ + s += strlen(cmd); + while(*s==' ' || *s=='\t' || *s=='\n') + s++; + return s; +} + +//#define PLAYSTRING "/^[0-9:]+>" +//#define PLAYSTRINGSPACE "/^[0-9:]+> ?" +//#define INITSTRING "0:00> " + +#define INITSTRING "> " +#define PLAYSTRING "/^>" +#define PLAYSTRINGSPACE "/^> ?" + +/* + * find the playing string, leave in addr + * if q0, q1 are non-nil, set them to the addr of the string. + */ +int +findplay(Window *w, char *s, ulong *q0, ulong *q1) +{ + char xbuf[25]; + if(w->data < 0) + w->data = winopenfile(w, "data"); + + if(!winsetaddr(w, "#0", 1) || !winsetaddr(w, s, 1)) + return 0; + + seek(w->addr, 0, 0); + if(read(w->addr, xbuf, 24) != 24) + return 0; + + xbuf[24] = 0; + if(q0) + *q0 = atoi(xbuf); + if(q1) + *q1 = atoi(xbuf+12); + + return 1; +} + +/* + * find the playing string and replace the time + */ +int +setplaytime(Window *w, char *new) +{ + char buf[40]; + ulong q0, q1; + +return 1; + if(!findplay(w, PLAYSTRING, &q0, &q1)) + return 0; + + q1--; /* > */ + sprint(buf, "#%lud,#%lud", q0, q1); + DPRINT(2, "setaddr %s\n", buf); + if(!winsetaddr(w, buf, 1)) + return 0; + + if(write(w->data, new, strlen(new)) != strlen(new)) + return 0; + + return 1; +} + +/* + * find the playing string, and remove it. + * return the string at the beginning of hte next line in buf + * (presumably a track number). + */ +static int +unmarkplay(Window *w, char *buf, int n, ulong *q0, ulong *q1, ulong *qbegin) +{ + char xbuf[24]; + + if(!findplay(w, PLAYSTRINGSPACE, q0, q1)) + return 0; + + if(write(w->data, "", 0) < 0 || !winsetaddr(w, "+1+#0", 1)) + return 0; + + if(qbegin) { + seek(w->addr, 0, 0); + if(read(w->addr, xbuf, 24) != 24) + return 0; + *qbegin = atoi(xbuf); + } + + if(buf) { + if((n = read(w->data, buf, n-1)) < 0) + return 0; + + buf[n] = '\0'; + } + + return 1; +} + +int +markplay(Window *w, ulong q0) +{ + char buf[20]; + + if(w->data < 0) + w->data = winopenfile(w, "data"); + + sprint(buf, "#%lud", q0); + DPRINT(2, "addr %s\n", buf); + if(!winsetaddr(w, buf, 1) || !winsetaddr(w, "-0", 1)) + return 0; + if(write(w->data, INITSTRING, strlen(INITSTRING)) != strlen(INITSTRING)) + return 0; + return 1; +} + +/* return 1 if handled, 0 otherwise */ +int +cdcommand(Window *w, Drive *d, char *s) +{ + s = skip(s, ""); + + if(iscmd(s, "Del")){ + if(windel(w, 0)) + threadexitsall(nil); + return 1; + } + if(iscmd(s, "Stop")){ + unmarkplay(w, nil, 0, nil, nil, nil); + stop(d); + return 1; + } + if(iscmd(s, "Eject")){ + unmarkplay(w, nil, 0, nil, nil, nil); + eject(d); + return 1; + } + if(iscmd(s, "Ingest")){ + unmarkplay(w, nil, 0, nil, nil, nil); + ingest(d); + return 1; + } + if(iscmd(s, "Pause")){ + pause(d); + return 1; + } + if(iscmd(s, "Resume")){ + resume(d); + return 1; + } + return 0; +} + +void +drawtoc(Window *w, Drive *d, Toc *t) +{ + int i, playing; + + if(w->data < 0) + w->data = winopenfile(w, "data"); + if(!winsetaddr(w, ",", 1)) + return; + + fprint(w->data, "Title\n\n"); + playing = -1; + if(d->status.state == Splaying || d->status.state == Spaused) + playing = d->status.track-t->track0; + + for(i=0; intrack; 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; intrack; i++) { + if(t->track[i].title) { + sprint(old, "/Track %d", i+1); + if(winsetaddr(w, old, 1)) + write(w->data, t->track[i].title, strlen(t->track[i].title)); + } + } +} + +void +advancetrack(Drive *d, Window *w) +{ + int n; + ulong q0, q1, qnext; + char buf[20]; + + q0 = q1 = 0; + if(!unmarkplay(w, buf, sizeof(buf), &q0, &q1, &qnext)) { + DPRINT(2, "unmark: %r\n"); + return; + } + + DPRINT(2, "buf: %s\n", buf); + if(strncmp(buf, "repeat", 6) == 0) { + if(!winsetaddr(w, "#0", 1) || !findplay(w, "/^[0-9]+\\/", &qnext, nil)) { + DPRINT(2, "set/find: %r\n"); + return; + } + if(w->data < 0) + w->data = winopenfile(w, "data"); + if((n = read(w->data, buf, sizeof(buf)-1)) <= 0) { + DPRINT(2, "read %d: %r\n", n); + return; + } + buf[n] = 0; + DPRINT(2, "buf: %s\n", buf); + } + + if((n = atoi(buf)) == 0) + return; + + if(!markplay(w, qnext)) + DPRINT(2, "err: %r"); + + playtrack(d, n-1, n-1); +} + +void +acmeevent(Drive *d, Window *w, Event *e) +{ + Event *ea, *e2, *eq; + char *s, *t, *buf; + int n, na; + ulong q0, q1; + + switch(e->c1){ /* origin of action */ + default: + Unknown: + fprint(2, "unknown message %c%c\n", e->c1, e->c2); + break; + + case 'E': /* write to body or tag; can't affect us */ + break; + + case 'F': /* generated by our actions; ignore */ + break; + + case 'K': /* type away; we don't care */ + break; + + case 'M': /* mouse event */ + switch(e->c2){ /* type of action */ + case 'x': /* mouse: button 2 in tag */ + case 'X': /* mouse: button 2 in body */ + ea = nil; + // e2 = nil; + s = e->b; + if(e->flag & 2){ /* null string with non-null expansion */ + e2 = recvp(w->cevent); + if(e->nb==0) + s = e2->b; + } + if(e->flag & 8){ /* chorded argument */ + ea = recvp(w->cevent); /* argument */ + na = ea->nb; + recvp(w->cevent); /* ignore origin */ + }else + na = 0; + + /* append chorded arguments */ + if(na){ + t = emalloc(strlen(s)+1+na+1); + sprint(t, "%s %s", s, ea->b); + s = t; + } + /* if it's a known command, do it */ + /* if it's a long message, it can't be for us anyway */ + DPRINT(2, "exec: %s\n", s); + if(!cdcommand(w, d, s)) /* send it back */ + winwriteevent(w, e); + if(na) + free(s); + break; + + case 'l': /* mouse: button 3 in tag */ + case 'L': /* mouse: button 3 in body */ + // buf = nil; + eq = e; + if(e->flag & 2){ + e2 = recvp(w->cevent); + eq = e2; + } + s = eq->b; + if(eq->q1>eq->q0 && eq->nb==0){ + buf = emalloc((eq->q1-eq->q0)*UTFmax+1); + winread(w, eq->q0, eq->q1, buf); + s = buf; + } + DPRINT(2, "load %s\n", s); + if((n = atoi(s)) != 0) { + DPRINT(2, "mark %d\n", n); + q0 = q1 = 0; + unmarkplay(w, nil, 0, &q0, &q1, nil); + + /* adjust eq->q* for deletion */ + if(eq->q0 > q1) { + eq->q0 -= (q1-q0); + eq->q1 -= (q1-q0); + } + if(!markplay(w, eq->q0)) + DPRINT(2, "err: %r\n"); + + playtrack(d, n-1, n-1); + } else + winwriteevent(w, e); + break; + + case 'i': /* mouse: text inserted in tag */ + case 'I': /* mouse: text inserted in body */ + case 'd': /* mouse: text deleted from tag */ + case 'D': /* mouse: text deleted from body */ + break; + + default: + goto Unknown; + } + } +} diff --git a/acme/bin/source/acd/cddb b/acme/bin/source/acd/cddb new file mode 100644 index 000000000..be909a8f0 --- /dev/null +++ b/acme/bin/source/acd/cddb @@ -0,0 +1,206 @@ + + +::freedb.org:: + + + + +
+ + + + + +
+ + + +
+ + + +
+ + + +
freedb.org
+
a free approach to cddbp +
+
+
+
+
+ +
  

+
+
+freedb.org - a free approach to cddbp

+ + + + + +
+ + + + + + + + +
+ + + + + + + +
Main Menu
+ + + + + + + +
+ + +
+
  • Home +
  • News-Topics +
  • About +
  • Developers +
  • Applications +
  • Download +
  • Forum +
  • Web-based Search +
  • Web Links +
  • Your Account +
  • Submit News + +
  • + + +
    +
    +
    + + +
    + + + + + + + +
    FAQ
    + + + + + + + +
    + + +
    + Our FAQ can be found here.
    +Please read the FAQ before asking questions via email.
    + + +
    +
    +
    + + +
    + + + + + + + +
    Contact
    + + + + + + + +
    + + +
    + General questions:
    +info@freedb.org
    +Databaseupdates:
    +updates@freedb.org
    +(NOT for submission!)
    +Please keep in mind that we are NOT the Nero-Support and please do not send CD-submits to the adresses above.
    +Submits have to go to:
    +freedb-submit@freedb.org
    + + +
    +
    +
    + + + +
    + + + + + + + +
    Downloads
    + + + + + + + +
    + + +
    + The link to the database downloads is here
    + + +
    +
    +
      + + +
    +
    + + + +
    + Database-format specification
    + +

    + Due to problems with using backslashes on our Webpage, we cannot display the database-format specification directly here.
    +But you can find it here as a text-document. +
    +   +
     


    + +

    +
    +
    +
    +
    + + diff --git a/acme/bin/source/acd/cddb.c b/acme/bin/source/acd/cddb.c new file mode 100644 index 000000000..ba1fa7604 --- /dev/null +++ b/acme/bin/source/acd/cddb.c @@ -0,0 +1,197 @@ +#include "acd.h" +#include + +/* see CDDBPROTO */ +static ulong +cddb_sum(int n) +{ + int ret; + ret = 0; + while(n > 0) { + ret += n%10; + n /= 10; + } + return ret; +} + +static ulong +diskid(Toc *t) +{ + int i, n, tmp; + Msf *ms, *me; + + n = 0; + for(i=0; i < t->ntrack; i++) + n += cddb_sum(t->track[i].start.m*60+t->track[i].start.s); + + ms = &t->track[0].start; + me = &t->track[t->ntrack].start; + tmp = (me->m*60+me->s) - (ms->m*60+ms->s); + + /* + * the spec says n%0xFF rather than n&0xFF. it's unclear which is correct. + * most CDs are in the database under both entries. + */ + return ((n & 0xFF) << 24 | (tmp << 8) | t->ntrack); +} + +static void +append(char **d, char *s) +{ + char *r; + if (*d == nil) + *d = estrdup(s); + else { + r = emalloc(strlen(*d) + strlen(s) + 1); + strcpy(r, *d); + strcat(r, s); + free(*d); + *d = r; + } +} + +static int +cddbfilltoc(Toc *t) +{ + int fd; + int i; + char *p, *q; + Biobuf bin; + Msf *m; + char *f[10]; + int nf; + char *id, *categ; + char gottrack[MTRACK]; + int gottitle; + + fd = dial("tcp!freedb.freedb.org!888", 0, 0, 0); + if(fd < 0) { + fprint(2, "cannot dial: %r\n"); + return -1; + } + Binit(&bin, fd, OREAD); + + if((p=Brdline(&bin, '\n')) == nil || atoi(p)/100 != 2) { + died: + close(fd); + Bterm(&bin); + fprint(2, "error talking to server\n"); + if(p) { + p[Blinelen(&bin)-1] = 0; + fprint(2, "server says: %s\n", p); + } + return -1; + } + + fprint(fd, "cddb hello gre plan9 9cd 1.0\r\n"); + if((p = Brdline(&bin, '\n')) == nil || atoi(p)/100 != 2) + goto died; + + fprint(fd, "cddb query %8.8lux %d", diskid(t), t->ntrack); + DPRINT(2, "cddb query %8.8lux %d", diskid(t), t->ntrack); + for(i=0; intrack; i++) { + m = &t->track[i].start; + fprint(fd, " %d", (m->m*60+m->s)*75+m->f); + DPRINT(2, " %d", (m->m*60+m->s)*75+m->f); + } + m = &t->track[t->ntrack-1].end; + fprint(fd, " %d\r\n", m->m*60+m->s); + DPRINT(2, " %d\r\n", m->m*60+m->s); + + if((p = Brdline(&bin, '\n')) == nil || atoi(p)/100 != 2) + goto died; + p[Blinelen(&bin)-1] = 0; + DPRINT(2, "cddb: %s\n", p); + nf = tokenize(p, f, nelem(f)); + if(nf < 1) + goto died; + + switch(atoi(f[0])) { + case 200: /* exact match */ + if(nf < 3) + goto died; + categ = f[1]; + id = f[2]; + break; + case 211: /* close matches */ + if((p = Brdline(&bin, '\n')) == nil) + goto died; + if(p[0] == '.') /* no close matches? */ + goto died; + p[Blinelen(&bin)-1] = '\0'; + + /* accept first match */ + nf = tokenize(p, f, nelem(f)); + if(nf < 2) + goto died; + categ = f[0]; + id = f[1]; + + /* snarf rest of buffer */ + while(p[0] != '.') { + if((p = Brdline(&bin, '\n')) == nil) + goto died; + p[Blinelen(&bin)-1] = '\0'; + DPRINT(2, "cddb: %s\n", p); + } + break; + case 202: /* no match */ + default: + goto died; + } + + /* fetch results for this cd */ + fprint(fd, "cddb read %s %s\r\n", categ, id); + + memset(gottrack, 0, sizeof(gottrack)); + gottitle = 0; + do { + if((p = Brdline(&bin, '\n')) == nil) + goto died; + q = p+Blinelen(&bin)-1; + while(isspace(*q)) + *q-- = 0; +DPRINT(2, "cddb %s\n", p); + if(strncmp(p, "DTITLE=", 7) == 0) { + if (gottitle) + append(&t->title, p + 7); + else + t->title = estrdup(p+7); + gottitle = 1; + } else if(strncmp(p, "TTITLE", 6) == 0 && isdigit(p[6])) { + i = atoi(p+6); + if(i < t->ntrack) { + p += 6; + while(isdigit(*p)) + p++; + if(*p == '=') + p++; + + if (gottrack[i]) + append(&t->track[i].title, p); + else + t->track[i].title = estrdup(p); + gottrack[i] = 1; + } + } + } while(*p != '.'); + + fprint(fd, "quit\r\n"); + close(fd); + Bterm(&bin); + + return 0; +} + +void +cddbproc(void *v) +{ + Drive *d; + Toc t; + + threadsetname("cddbproc"); + d = v; + while(recv(d->cdbreq, &t)) + if(cddbfilltoc(&t) == 0) + send(d->cdbreply, &t); +} diff --git a/acme/bin/source/acd/cddbproto b/acme/bin/source/acd/cddbproto new file mode 100644 index 000000000..d1a278318 --- /dev/null +++ b/acme/bin/source/acd/cddbproto @@ -0,0 +1,894 @@ + + +::freedb.org:: + + + + +
    + + + + + +
    + + + +
    + + + +
    + + + +
    freedb.org
    +
    a free approach to cddbp +
    +
    +
    +
    +
    + +
      

    +
    +
    +freedb.org - a free approach to cddbp

    + + + + + +
    + + + + + + + + +
    + + + + + + + +
    Main Menu
    + + + + + + + +
    + + +
    +
  • Home +
  • News-Topics +
  • About +
  • Developers +
  • Applications +
  • Download +
  • Forum +
  • Web-based Search +
  • Web Links +
  • Your Account +
  • Submit News + +
  • + + +
    +
    +
    + + +
    + + + + + + + +
    FAQ
    + + + + + + + +
    + + +
    + Our FAQ can be found here.
    +Please read the FAQ before asking questions via email.
    + + +
    +
    +
    + + +
    + + + + + + + +
    Contact
    + + + + + + + +
    + + +
    + General questions:
    +info@freedb.org
    +Databaseupdates:
    +updates@freedb.org
    +(NOT for submission!)
    +Please keep in mind that we are NOT the Nero-Support and please do not send CD-submits to the adresses above.
    +Submits have to go to:
    +freedb-submit@freedb.org
    + + +
    +
    +
    + + + +
    + + + + + + + +
    Downloads
    + + + + + + + +
    + + +
    + The link to the database downloads is here
    + + +
    +
    +
      + + +
    +
    + + + +
    + CDDB-protocol documentation
    + +

    +
    +				CDDB Protocol
    +
    +			  By Steve Scherf and Ti Kan
    +		          --------------------------
    +
    +Revision: $Id: CDDBPROTO,v 1.6 1997/05/14 07:53:52 steve Exp steve $
    +
    +
    +Notation:
    +-> : client to server
    +<- : 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:
    +----------------------
    +<- 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:
    +-> 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:
    +<- 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:
    +-> 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:
    +<- code categ discid dtitle
    +	or
    +<- code close matches found
    +<- categ discid dtitle
    +<- categ discid dtitle
    +<- (more matches...)
    +<- .
    +
    +    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:
    +-> cddb read categ discid
    +
    +    categ:
    +	CD category.  Example: rock
    +    discid:
    +	CD disc ID number.  Example: f50a3b13
    +
    +Server response:
    +<- code categ discid
    +<- # xmcd 2.0 CD database file
    +<- # ...
    +<- (CDDB data...)
    +<- .
    +	or
    +<- 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:
    +-> 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:
    +<- code matches found
    +<- categ discid dtitle
    +<- categ discid dtitle
    +<- (more matches...)
    +<- .
    +
    +    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:
    +-> cddb write categ discid
    +
    +    categ:
    +	CD category.  Example: rock
    +    discid:
    +	CD disc ID number.  Example: f50a3b13
    +
    +Server response:
    +<- 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:
    +-> # xmcd 2.0 CD database file
    +-> # ...
    +-> (CDDB data)
    +-> .
    +
    +Server response:
    +<- 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:
    +-> help
    +	or
    +-> help cmd
    +
    +    cmd:
    +	CDDB command.  Example: quit
    +
    +	or
    +
    +-> help cmd subcmd
    +
    +    cmd:
    +	CDDB command.  Example: cddb
    +    subcmd:
    +	CDDB command argument.  Example: query
    +
    +Server response:
    +<- code Help information follows
    +<- (help data ...)
    +<- .
    +	or
    +<- code no help information available
    +
    +    code:
    +	210	OK, help information follows (until terminating marker)
    +	401	No help information available
    +
    +
    +Log statistics:
    +---------------
    +Client command:
    +-> 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:
    +<- code Log summary follows
    +<- (log stats)
    +<- .
    +	or
    +<- code Log follows
    +<- (log stats)
    +<- .
    +
    +    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:
    +-> motd
    +
    +Server response:
    +<- code Last modified: date MOTD follows (until terminating marker)
    +<- (message text)
    +<- .
    +
    +    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:
    +-> proto [level]
    +
    +    level:
    +	The (numerical) protocol level to set the server to.
    +
    +Server response:
    +<- code CDDB protocol level: current cur_level, supported supported_level
    +	or
    +<- 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:
    +-> sites
    +
    +Server response:
    +<- code OK, site information follows (until terminating `.')
    +<- (data)
    +<- .
    +
    +    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:
    +-> stat
    +
    +Server response:
    +<- code OK, status information follows (until terminating `.')
    +<- (data)
    +<- .
    +
    +    code:
    +	210	Ok, status information follows
    +
    +    The possible data is as follows:
    +	current proto: <current_level>
    +	    An integer representing the server's current operating protocol
    +	    level.
    +	max proto:     <max_level>
    +	    The maximum supported protocol level.
    +	gets:          <yes | no>
    +	    Whether or not the client is allowed to get log information,
    +	    according to the string "yes" or "no".
    +	updates:       <yes | no>
    +	    Whether or not the client is allowed to initiate a database
    +	    update, according to the string "yes" or "no".
    +	posting:       <yes | no>
    +	    Whether or not the client is allowed to post new entries,
    +	    according to the string "yes" or "no".
    +	quotes:        <yes | no>
    +	    Whether or not quoted arguments are enabled, according to
    +	    the string "yes" or "no".
    +	current users: <num_users>
    +	    The number of users currently connected to the server.
    +	max users:     <num_max_users>
    +	    The number of users that can concurrently connect to the server.
    +	strip ext:	<yes | no>
    +	    Whether or not extended data is stripped by the server before
    +	    presented to the user.
    +	Database entries: <num_db_entries>
    +	    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:
    +
    +		<white space>catgory: <num_db_entries>
    +
    +	    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:
    +
    +		<white space>site: <num_db_entries>
    +
    +	    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:
    +-> ver
    +
    +Server response:
    +<- code servername version copyright
    +	or
    +<- 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:
    +-> update
    +
    +Server response:
    +<- code Updating the database.
    +	or
    +<- code Permission denied.
    +	or
    +<- code Unable to update the database.
    +
    +    code:
    +	200 Updating the database.
    +	401 Permission denied.
    +	402 Unable to update the database.
    +
    +
    +Server users:
    +-------------
    +Client command:
    +-> whom
    +
    +Server response:
    +<- code User list follows
    +
    +    code:
    +	210	OK, user list follows (until terminating marker)
    +	401	No user information available.
    +
    +
    +Client sign-off:
    +----------------
    +Client command:
    +-> quit
    +
    +Server response:
    +<- code hostname closing connection.  Goodbye.
    +
    +    code:
    +	230	OK, goodbye.
    +    hostname:
    +	Server host name.  Example: xyz.fubar.com
    +
    +
    +General errors:
    +---------------
    +
    +Server response:
    +<- 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&hello=joe+my.host.com+clientname+version&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
    +"&" 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&hello=joe+my.host.com+xmcd+2.1&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.
    +
    +
    +   +
     


    + +

    +
    +
    +
    +
    + + diff --git a/acme/bin/source/acd/discid b/acme/bin/source/acd/discid new file mode 100644 index 000000000..1bc9dbf66 --- /dev/null +++ b/acme/bin/source/acd/discid @@ -0,0 +1,159 @@ + +CDDB DISCID +----------- + +Both forms of CDDB access require that the software compute a "disc +ID" which is an identifier that is used to access the CDDB. The disc +ID is a 8-digit hexadecimal (base-16) number, computed using data from +a CD's Table-of-Contents (TOC) in MSF (Minute Second Frame) form. The +algorithm is listed below in Appendix A. + +It is crucial that your software compute the disc ID correctly. If it +does not generate the correct disc ID, it will not be compatible with CDDB. +Moreover, if your software submits CDDB entries with bad disc IDs to the +CDDB archives, it could compromise the integrity of the CDDB. + +[...] + +APPENDIX A - CDDB DISCID ALGORITHM +---------------------------------- + +The following is a C code example that illustrates how to generate the +CDDB disc ID. [...] A text description +of the algorithm follows, which should contain the necessary information +to code the algorithm in any programming language. + + +struct toc { + int min; + int sec; + int frame; +}; + +struct toc cdtoc[100]; + +int +read_cdtoc_from_drive(void) +{ + /* Do whatever is appropriate to read the TOC of the CD + * into the cdtoc[] structure array. + */ + return (tot_trks); +} + +int +cddb_sum(int n) +{ + int ret; + + /* For backward compatibility this algorithm must not change */ + + ret = 0; + + while (n > 0) { + ret = ret + (n % 10); + n = n / 10; + } + + return (ret); +} + +unsigned long +cddb_discid(int tot_trks) +{ + int i, + t = 0, + n = 0; + + /* For backward compatibility this algorithm must not change */ + + i = 0; + + while (i < tot_trks) { + n = n + cddb_sum((cdtoc[i].min * 60) + cdtoc[i].sec); + i++; + } + + t = ((cdtoc[tot_trks].min * 60) + cdtoc[tot_trks].sec) - + ((cdtoc[0].min * 60) + cdtoc[0].sec); + + return ((n % 0xff) << 24 | t << 8 | tot_trks); +} + +main() +{ + int tot_trks; + + tot_trks = read_cdtoc_from_drive(); + printf("The discid is %08x", cddb_discid(tot_trks)); +} + + +This code assumes that your compiler and architecture support 32-bit +integers. + +The cddb_discid function computes the discid based on the CD's TOC data +in MSF form. The frames are ignored for this purpose. The function is +passed a parameter of tot_trks (which is the total number of tracks on +the CD), and returns the discid integer number. + +It is assumed that cdtoc[] is an array of data structures (records) +containing the fields min, sec and frame, which are the minute, second +and frame offsets (the starting location) of each track. This +information is read from the TOC of the CD. There are actually +tot_trks + 1 "active" elements in the array, the last one being the +offset of the lead-out (also known as track 0xAA). + +The function loops through each track in the TOC, and for each track +it takes the (M * 60) + S (total offset in seconds) of the track and +feeds it to cddb_sum() function, which simply adds the value of each digit +in the decimal string representation of the number. A running sum of this +result for each track is kept in the variable n. + +At the end of the loop: +1. t is calculated by subtracting the (M * 60) + S offset of the lead-out +minus the (M * 60) + S offset of first track (yielding the length of +the disc in seconds). + +2. The result of (n modulo FFh) is left-shifted by 24 bits. + +3. t is left shifted by 8. + +The bitwise-OR operation of result 2., 3. and the tot_trks number is +used as the discid. + +The discid is represented in hexadecimal form for the purpose of +xmcd cddb file names and the DISCID= field in the xmcd cddb file itself. +If the hexadecimal string is less than 8 characters long, it is +zero-padded to 8 characters (i.e., 3a8f07 becomes 003a8f07). All +alpha characters in the string should be in lower case, where +applicable. + +Important note for clients using the MS-Windows MCI interface: + +The Windows MCI interface does not provide the MSF location of the +lead-out. Thus, you must compute the lead-out location by taking the +starting position of the last track and add the length of the last track +to it. However, the MCI interface returns the length of the last track +as ONE FRAME SHORT of the actual length found in the CD's TOC. In most +cases this does not affect the disc ID generated, because we truncate +the frame count when computing the disc ID anyway. However, if the +lead-out track has an actual a frame count of 0, the computed quantity +(based on the MSF data returned from the MCI interface) would result in +the seconds being one short and the frame count be 74. For example, +a CD with the last track at an offset of 48m 32s 12f and having a +track length of 2m 50s 63f has a lead-out offset of 51m 23s 0f. Windows +MCI incorrectly reports the length as 2m 50s 62f, which would yield a +lead-out offset of 51m 22s 74f, which causes the resulting truncated +disc length to be off by one second. This will cause an incorrect disc +ID to be generated. You should thus add one frame to the length of the +last track when computing the location of the lead-out. + +The easiest way for Windows clients to compute the lead-out given information +in MSF format is like this: + +(offset_minutes * 60 * 75) + (offset_seconds * 75) + offset_frames + +(length_minutes * 60 * 75) + (length_seconds * 75) + length_frames + 1 = X + +Where X is the offset of the lead-out in frames. To find the lead-out in +seconds, simply divide by 75 and discard the remainder. diff --git a/acme/bin/source/acd/mailinglist b/acme/bin/source/acd/mailinglist new file mode 100644 index 000000000..002fd8da8 --- /dev/null +++ b/acme/bin/source/acd/mailinglist @@ -0,0 +1,220 @@ + + +::freedb.org:: + + + + +
    + + + + + +
    + + + +
    + + + +
    + + + +
    freedb.org
    +
    a free approach to cddbp +
    +
    +
    +
    +
    + +
      

    +
    +
    +freedb.org - a free approach to cddbp

    + + + + + +
    + + + + + + + + +
    + + + + + + + +
    Main Menu
    + + + + + + + +
    + + +
    +
  • Home +
  • News-Topics +
  • About +
  • Developers +
  • Applications +
  • Download +
  • Forum +
  • Web-based Search +
  • Web Links +
  • Your Account +
  • Submit News + +
  • + + +
    +
    +
    + + +
    + + + + + + + +
    FAQ
    + + + + + + + +
    + + +
    + Our FAQ can be found here.
    +Please read the FAQ before asking questions via email.
    + + +
    +
    +
    + + +
    + + + + + + + +
    Contact
    + + + + + + + +
    + + +
    + General questions:
    +info@freedb.org
    +Databaseupdates:
    +updates@freedb.org
    +(NOT for submission!)
    +Please keep in mind that we are NOT the Nero-Support and please do not send CD-submits to the adresses above.
    +Submits have to go to:
    +freedb-submit@freedb.org
    + + +
    +
    +
    + + + +
    + + + + + + + +
    Downloads
    + + + + + + + +
    + + +
    + The link to the database downloads is here
    + + +
    +
    +
      + + +
    +
    + + + +
    + Mailinglists
    + +

    + There are a couple of mailinglists available: + +

    fdb-apps@freedb.org

    + +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
    +fdb-apps-request@freedb.org
    +with the word "subscribe" in the body of the email. +

    fdb-dev@freedb.org

    + +This list is for anyone interested in developing the freedb.org +server software. Subcribe +to this list by sending an email to
    +fdb-dev-request@freedb.org
    +with the word "subscribe" in the body of the email. +
    +   +
     


    + +

    +
    +
    +
    +
    + + diff --git a/acme/bin/source/acd/main.c b/acme/bin/source/acd/main.c new file mode 100644 index 000000000..2ac388f56 --- /dev/null +++ b/acme/bin/source/acd/main.c @@ -0,0 +1,135 @@ +#include "acd.h" + +int debug; + +void +usage(void) +{ + fprint(2, "usage: acd dev\n"); + threadexitsall("usage"); +} + +Alt +mkalt(Channel *c, void *v, int op) +{ + Alt a; + + memset(&a, 0, sizeof(a)); + a.c = c; + a.v = v; + a.op = op; + return a; +} + +void +freetoc(Toc *t) +{ + int i; + + free(t->title); + for(i=0; intrack; i++) + free(t->track[i].title); +} + +void +eventwatcher(Drive *d) +{ + enum { STATUS, WEVENT, TOCDISP, DBREQ, DBREPLY, NALT }; + Alt alts[NALT+1]; + Toc nt, tdb; + Event *e; + Window *w; + Cdstatus s; + char buf[40]; + + w = d->w; + + alts[STATUS] = mkalt(d->cstatus, &s, CHANRCV); + alts[WEVENT] = mkalt(w->cevent, &e, CHANRCV); + alts[TOCDISP] = mkalt(d->ctocdisp, &nt, CHANRCV); + alts[DBREQ] = mkalt(d->cdbreq, &tdb, CHANNOP); + alts[DBREPLY] = mkalt(d->cdbreply, &nt, CHANRCV); + alts[NALT] = mkalt(nil, nil, CHANEND); + for(;;) { + switch(alt(alts)) { + case STATUS: + //DPRINT(2, "s..."); + d->status = s; + if(s.state == Scompleted) { + s.state = Sunknown; + advancetrack(d, w); + } + //DPRINT(2, "status %d %d %d %M %M\n", s.state, s.track, s.index, s.abs, s.rel); + sprint(buf, "%d:%2.2d", s.rel.m, s.rel.s); + setplaytime(w, buf); + break; + case WEVENT: + //DPRINT(2, "w..."); + acmeevent(d, w, e); + break; + case TOCDISP: + //DPRINT(2,"td..."); + freetoc(&d->toc); + d->toc = nt; + drawtoc(w, d, &d->toc); + tdb = nt; + alts[DBREQ].op = CHANSND; + break; + case DBREQ: /* sent */ + //DPRINT(2,"dreq..."); + alts[DBREQ].op = CHANNOP; + break; + case DBREPLY: + //DPRINT(2,"drep..."); + freetoc(&d->toc); + d->toc = nt; + redrawtoc(w, &d->toc); + break; + } + } +} + +void +threadmain(int argc, char **argv) +{ + Scsi *s; + Drive *d; + char buf[80]; + + ARGBEGIN{ + case 'v': + debug++; + scsiverbose++; + }ARGEND + + if(argc != 1) + usage(); + + fmtinstall('M', msfconv); + + if((s = openscsi(argv[0])) == nil) + error("opening scsi: %r"); + + d = malloc(sizeof(*d)); + if(d == nil) + error("out of memory"); + memset(d, 0, sizeof d); + + d->scsi = s; + d->w = newwindow(); + d->ctocdisp = chancreate(sizeof(Toc), 0); + d->cdbreq = chancreate(sizeof(Toc), 0); + d->cdbreply = chancreate(sizeof(Toc), 0); + d->cstatus = chancreate(sizeof(Cdstatus), 0); + + proccreate(wineventproc, d->w, STACK); + proccreate(cddbproc, d, STACK); + proccreate(cdstatusproc, d, STACK); + + cleanname(argv[0]); + snprint(buf, sizeof(buf), "%s/", argv[0]); + winname(d->w, buf); + + wintagwrite(d->w, "Stop Pause Resume Eject Ingest ", 5+6+7+6+7); + eventwatcher(d); +} diff --git a/acme/bin/source/acd/mkfile b/acme/bin/source/acd/mkfile new file mode 100644 index 000000000..7b549a6c5 --- /dev/null +++ b/acme/bin/source/acd/mkfile @@ -0,0 +1,22 @@ +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; intrack; 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; intrack; 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; intrack; i++) + print("%d. %M-%M (%lud-%lud)\n", i+1, + t->track[i].start, t->track[i].end, + t->track[i].bstart, t->track[i].bend); +} + +static void +ping(Drive *d) +{ + uchar cmd[12]; + + memset(cmd, 0, sizeof cmd); + cmd[0] = 0x43; + scsi(d->scsi, cmd, sizeof(cmd), nil, 0, Snone); +} + +static int +playstatus(Drive *d, Cdstatus *stat) +{ + uchar cmd[12], resp[16]; + + memset(cmd, 0, sizeof cmd); + cmd[0] = 0x42; + cmd[1] = 0x02; + cmd[2] = 0x40; + cmd[3] = 0x01; + cmd[7] = sizeof(resp)>>8; + cmd[8] = sizeof(resp); + if(scsi(d->scsi, cmd, sizeof(cmd), resp, sizeof(resp), Sread) < 0) + return -1; + + switch(resp[1]){ + case 0x11: + stat->state = Splaying; + break; + case 0x12: + stat->state = Spaused; + break; + case 0x13: + stat->state = Scompleted; + break; + case 0x14: + stat->state = Serror; + break; + case 0x00: /* not supported */ + case 0x15: /* no current status to return */ + default: + stat->state = Sunknown; + break; + } + + stat->track = resp[6]; + stat->index = resp[7]; + stat->abs = rdmsf(resp+9); + stat->rel = rdmsf(resp+13); + return 0; +} + +void +cdstatusproc(void *v) +{ + Drive *d; + Toc t; + Cdstatus s; + + t.changetime = ~0; + t.nchange = ~0; + + threadsetname("cdstatusproc"); + d = v; + DPRINT(2, "cdstatus %d\n", getpid()); + for(;;) { + ping(d); + //DPRINT(2, "d %d %d t %d %d\n", d->scsi->changetime, d->scsi->nchange, t.changetime, t.nchange); + if(playstatus(d, &s) == 0) + send(d->cstatus, &s); + if(d->scsi->changetime != t.changetime || d->scsi->nchange != t.nchange) { + if(gettoc(d->scsi, &t) == 0) { + DPRINT(2, "sendtoc...\n"); + if(debug) dumptoc(&t); + send(d->ctocdisp, &t); + } else + DPRINT(2, "error: %r\n"); + } + sleep(1000); + } +} diff --git a/acme/bin/source/acd/outline b/acme/bin/source/acd/outline new file mode 100644 index 000000000..5ab337d9c --- /dev/null +++ b/acme/bin/source/acd/outline @@ -0,0 +1,32 @@ +acd is composed of four procs + +wineventproc (win.c:/^wineventproc) + reads acme window events, sends them along w->cevent. + +cdstatusproc (mmc.c:/^cdstatusproc) + reads cd status once per second, sending + status updates to d->cstatus. + detects disk changes, sends new tocs to d->ctocdisp. + +cddbproc (cddb.c:/^cddbproc) + reads tocs from d->cdbreq, if it finds + translations in the cddb, sends new tocs to d->cdbreply. + +eventwatcher (main.c:/^eventwatcher) + the main event loop. + reads status from d->cstatus. + reads events from w->cevent. + reads new tocs to display from d->ctocdisp. + sends new tocs to translate to d->cdbreq. + reads new translated tocs from d->cdbreply. + +an interesting bug in the original design: + both cdstatusproc and the eventwatcher proc + issue scsi commands. (the eventwatcher responds to + things such as Play, Stop, etc., as well as advancing the track.) + + the sd(3) driver did not expect overlapped commands, + and crashed. + + this has been fixed by making the scsi(2) commands threadsafe, + and making the sd(3) driver more robust. diff --git a/acme/bin/source/acd/submit b/acme/bin/source/acd/submit new file mode 100644 index 000000000..49aaf1662 --- /dev/null +++ b/acme/bin/source/acd/submit @@ -0,0 +1,220 @@ +CDDB SUBMISSION +--------------- + +Your software may allow users to enter CDDB data and then submit them +to the freedb archive. +There are two methods of submission: via e-mail or via http using submit.cgi + +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 here. 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. + +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 "Discid howto" 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 below 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 CDDB server protocol for more information on +handling response codes. + +The body of the freedb entry being submitted should be sent verbatim as +described in the database-format specification. 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". + +CDDB submissions sent in test mode will be sanity-checked by the freedb server +and pass/fail confirmation sent back to the submitter, but will not actually +be deposited in the CD database. Please DO NOT send submisions in "submit" +mode until you have tested your program with several different CD's. diff --git a/acme/bin/source/acd/toc.c b/acme/bin/source/acd/toc.c new file mode 100644 index 000000000..c447949e6 --- /dev/null +++ b/acme/bin/source/acd/toc.c @@ -0,0 +1,59 @@ +#include "acd.h" + +Toc thetoc; + +void +tocthread(void *v) +{ + Drive *d; + + threadsetname("tocthread"); + d = v; + DPRINT(2, "recv ctocdisp?..."); + while(recv(d->ctocdisp, &thetoc) == 1) { + DPRINT(2, "recv ctocdisp!..."); + drawtoc(d->w, &thetoc); + DPRINT(2, "send dbreq...\n"); + send(d->ctocdbreq, &thetoc); + } +} + +void +freetoc(Toc *t) +{ + int i; + + free(t->title); + for(i=0; intrack; i++) + free(t->track[i].title); +} + +void +cddbthread(void *v) +{ + Drive *d; + Toc t; + + threadsetname("cddbthread"); + d = v; + while(recv(d->ctocdbreply, &t) == 1) { + if(thetoc.nchange == t.nchange) { + freetoc(&thetoc); + thetoc = t; + redrawtoc(d->w, &thetoc); + } + } +} + +void +cdstatusthread(void *v) +{ + Drive *d; + Cdstatus s; + + d = v; + + for(;;) + recv(d->cstat, &s); + +} diff --git a/acme/bin/source/acd/util.c b/acme/bin/source/acd/util.c new file mode 100644 index 000000000..2b300fc12 --- /dev/null +++ b/acme/bin/source/acd/util.c @@ -0,0 +1,89 @@ +#include "acd.h" + +void* +emalloc(uint n) +{ + void *p; + + p = malloc(n); + if(p == nil) + error("can't malloc: %r"); + memset(p, 0, n); + return p; +} + +char* +estrdup(char *s) +{ + char *t; + + t = emalloc(strlen(s)+1); + strcpy(t, s); + return t; +} + +char* +estrstrdup(char *s, char *t) +{ + char *u; + + u = emalloc(strlen(s)+strlen(t)+1); + strcpy(u, s); + strcat(u, t); + return u; +} + +char* +eappend(char *s, char *sep, char *t) +{ + char *u; + + if(t == nil) + u = estrstrdup(s, sep); + else{ + u = emalloc(strlen(s)+strlen(sep)+strlen(t)+1); + strcpy(u, s); + strcat(u, sep); + strcat(u, t); + } + free(s); + return u; +} + +char* +egrow(char *s, char *sep, char *t) +{ + s = eappend(s, sep, t); + free(t); + return s; +} + +void +error(char *fmt, ...) +{ + int n; + va_list arg; + char buf[256]; + + fprint(2, "Mail: "); + va_start(arg, fmt); + n = vsnprint(buf, sizeof buf, fmt, arg); + va_end(arg); + write(2, buf, n); + write(2, "\n", 1); + threadexitsall(fmt); +} + +void +ctlprint(int fd, char *fmt, ...) +{ + int n; + va_list arg; + char buf[256]; + + va_start(arg, fmt); + n = vsnprint(buf, sizeof buf, fmt, arg); + va_end(arg); + if(write(fd, buf, n) != n) + error("control file write error: %r"); +} diff --git a/acme/bin/source/acd/win.c b/acme/bin/source/acd/win.c new file mode 100644 index 000000000..2f378961c --- /dev/null +++ b/acme/bin/source/acd/win.c @@ -0,0 +1,320 @@ +#include "acd.h" + +Window* +newwindow(void) +{ + char buf[12]; + Window *w; + + w = emalloc(sizeof(Window)); + w->ctl = open("/mnt/wsys/new/ctl", ORDWR|OCEXEC); + if(w->ctl<0 || read(w->ctl, buf, 12)!=12) + error("can't open window ctl file: %r"); + ctlprint(w->ctl, "noscroll\n"); + w->id = atoi(buf); + w->event = winopenfile(w, "event"); + w->addr = -1; /* will be opened when needed */ + w->body = nil; + w->data = -1; + w->cevent = chancreate(sizeof(Event*), 0); + if(w->cevent == nil) + error("cevent is nil: %r"); + return w; +} + +void +winsetdump(Window *w, char *dir, char *cmd) +{ + if(dir != nil) + ctlprint(w->ctl, "dumpdir %s\n", dir); + if(cmd != nil) + ctlprint(w->ctl, "dump %s\n", cmd); +} + +void +wineventproc(void *v) +{ + Window *w; + int i; + + threadsetname("wineventproc"); + w = v; + for(i=0; ; i++){ + if(i >= NEVENT) + i = 0; + wingetevent(w, &w->e[i]); + sendp(w->cevent, &w->e[i]); + } +} + +int +winopenfile(Window *w, char *f) +{ + char buf[64]; + int fd; + + sprint(buf, "/mnt/wsys/%d/%s", w->id, f); + fd = open(buf, ORDWR|OCEXEC); + if(fd < 0) + error("can't open window file %s: %r", f); + return fd; +} + +void +wintagwrite(Window *w, char *s, int n) +{ + int fd; + + fd = winopenfile(w, "tag"); + if(write(fd, s, n) != n) + error("tag write: %r"); + close(fd); +} + +void +winname(Window *w, char *s) +{ + ctlprint(w->ctl, "name %s\n", s); +} + +void +winopenbody(Window *w, int mode) +{ + char buf[256]; + + sprint(buf, "/mnt/wsys/%d/body", w->id); + w->body = Bopen(buf, mode|OCEXEC); + if(w->body == nil) + error("can't open window body file: %r"); +} + +void +winclosebody(Window *w) +{ + if(w->body != nil){ + Bterm(w->body); + w->body = nil; + } +} + +void +winwritebody(Window *w, char *s, int n) +{ + if(w->body == nil) + winopenbody(w, OWRITE); + if(Bwrite(w->body, s, n) != n) + error("write error to window: %r"); +} + +int +wingetec(Window *w) +{ + if(w->nbuf == 0){ + w->nbuf = read(w->event, w->buf, sizeof w->buf); + if(w->nbuf <= 0){ + /* probably because window has exited, and only called by wineventproc, so just shut down */ + threadexits(nil); + } + w->bufp = w->buf; + } + w->nbuf--; + return *w->bufp++; +} + +int +wingeten(Window *w) +{ + int n, c; + + n = 0; + while('0'<=(c=wingetec(w)) && c<='9') + n = n*10+(c-'0'); + if(c != ' ') + error("event number syntax"); + return n; +} + +int +wingeter(Window *w, char *buf, int *nb) +{ + Rune r; + int n; + + r = wingetec(w); + buf[0] = r; + n = 1; + if(r >= Runeself) { + while(!fullrune(buf, n)) + buf[n++] = wingetec(w); + chartorune(&r, buf); + } + *nb = n; + return r; +} + +void +wingetevent(Window *w, Event *e) +{ + int i, nb; + + e->c1 = wingetec(w); + e->c2 = wingetec(w); + e->q0 = wingeten(w); + e->q1 = wingeten(w); + e->flag = wingeten(w); + e->nr = wingeten(w); + if(e->nr > EVENTSIZE) + error("event string too long"); + e->nb = 0; + for(i=0; inr; 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; iaddr < 0) + w->addr = winopenfile(w, "addr"); + if(w->data < 0) + w->data = winopenfile(w, "data"); + m = q0; + while(m < q1){ + n = sprint(buf, "#%d", m); + if(write(w->addr, buf, n) != n) + error("error writing addr: %r"); + n = read(w->data, buf, sizeof buf); + if(n <= 0) + error("reading data: %r"); + nr = nrunes(buf, n); + while(m+nr >q1){ + do; while(n>0 && (buf[--n]&0xC0)==0x80); + --nr; + } + if(n == 0) + break; + memmove(data, buf, n); + data += n; + *data = 0; + m += nr; + } +} + +void +windormant(Window *w) +{ + if(w->addr >= 0){ + close(w->addr); + w->addr = -1; + } + if(w->body != nil){ + Bterm(w->body); + w->body = nil; + } + if(w->data >= 0){ + close(w->data); + w->data = -1; + } +} + + +int +windel(Window *w, int sure) +{ + if(sure) + write(w->ctl, "delete\n", 7); + else if(write(w->ctl, "del\n", 4) != 4) + return 0; + /* event proc will die due to read error from event file */ + windormant(w); + close(w->ctl); + w->ctl = -1; + close(w->event); + w->event = -1; + return 1; +} + +void +winclean(Window *w) +{ + if(w->body) + Bflush(w->body); + ctlprint(w->ctl, "clean\n"); +} + +int +winsetaddr(Window *w, char *addr, int errok) +{ + if(w->addr < 0) + w->addr = winopenfile(w, "addr"); + if(write(w->addr, addr, strlen(addr)) < 0){ + if(!errok) + error("error writing addr(%s): %r", addr); + return 0; + } + return 1; +} + +int +winselect(Window *w, char *addr, int errok) +{ + if(winsetaddr(w, addr, errok)){ + ctlprint(w->ctl, "dot=addr\n"); + return 1; + } + return 0; +} + +char* +winreadbody(Window *w, int *np) /* can't use readfile because acme doesn't report the length */ +{ + char *s; + int m, na, n; + + if(w->body != nil) + winclosebody(w); + winopenbody(w, OREAD); + s = nil; + na = 0; + n = 0; + for(;;){ + if(na < n+512){ + na += 1024; + s = realloc(s, na+1); + } + m = Bread(w->body, s+n, na-n); + if(m <= 0) + break; + n += m; + } + s[n] = 0; + winclosebody(w); + *np = n; + return s; +} diff --git a/acme/bin/source/adict/_adict.c b/acme/bin/source/adict/_adict.c new file mode 100644 index 000000000..f2c14f5ea --- /dev/null +++ b/acme/bin/source/adict/_adict.c @@ -0,0 +1,584 @@ +#include +#include +#include +#include +#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 80) + threadprint(2, "Error in getting addres from dict.\n"); + else { + t = pbuffer; + /* remove trailing whitespace, newline */ + if (t != nil){ + while(*t != 0 && *t != '\n') + t++; + if(t == 0 && t > pbuffer) + t--; + while(t >= pbuffer && (*t==' ' || *t=='\n' || *t=='\t' || *t=='\r')) + *t-- = 0; + } + res = pbuffer; + } + close(fd); + return(res); +} + +char* +chgaddr(int dir) +{ + /* Increment or decrement the current address (curone). */ + + int fd; + char *res, *t; + + res = nil; + if (dir < 0) + sprint(buffer,"%s-a", curone); + else + sprint(buffer,"%s+a", curone); + fd = procrexec(xprog, "-d", dict, "-c", buffer, nil); + if (read(fd, abuffer, 80) > 80) + threadprint(2, "Error in getting addres from dict.\n"); + else { + res = abuffer; + while (*res != '#') res++; + t = res; + while ((*t != '\n') && (t != nil)) t++; + if (t != nil) *t = 0; + } + close(fd); + return(res); +} + +void +dispdicts(Win *cwin) +{ + /* Display available dictionaries in window. */ + + int fd, nb, i; + char buf[1024], *t; + + fd = procrexec(xprog, "-d", "?", nil); + wreplace(cwin, "0,$","",0); /* Clear window */ + while ((nb = read(fd, buf, 1024)) > 0) { + t = buf; + i = 0; + if (strncmp("Usage", buf, 5) == 0) { /* Remove first line. */ + while (t[0] != '\n') { + t++; + i++; + } + t++; + i++; + } + wwritebody(cwin, t, nb-i); + } + close(fd); + wclean(cwin); +} + +void +dispentry(Win *cwin) +{ + /* Display the current selection in window. */ + + int fd, nb; + char buf[BUFSIZE]; + + if (curone == nil) { + if (pattern != nil) { + sprint(buf,"Pattern not found.\n"); + wwritebody(cwin, buf, 19); + wclean(cwin); + } + return; + } + sprint(buffer,"%sp", curone); + fd = procrexec(xprog, "-d", dict, "-c", buffer, nil); + wreplace(cwin, "0,$","",0); /* Clear window */ + while ((nb = read(fd, buf, BUFSIZE)) > 0) { + wwritebody(cwin, buf, nb); + } + close(fd); + wclean(cwin); +} + +void +dispmatches(Win *cwin) +{ + /* Display the current matches. */ + + int fd, nb; + char buf[BUFSIZE]; + + sprint(buffer,"/%s/H", pattern); + fd = procrexec(xprog, "-d", dict, "-c", buffer, nil); + while ((nb = read(fd, buf, BUFSIZE)) > 0) + wwritebody(cwin, buf, nb); + close(fd); + wclean(cwin); +} + +char* +format(char *s) +{ + /* Format a string to be written in window tag. Acme doesn't like */ + /* non alpha-num's in the tag line. */ + + char *t, *h; + + t = fbuffer; + if (s == nil) { + *t = 0; + return t; + } + strcpy(t, s); + h = t; + while (*t != 0) { + if (!(((*t >= 'a') && (*t <= 'z')) || + ((*t >= 'A') && (*t <= 'Z')) || + ((*t >= '0') && (*t <= '9')))) + *t = '_'; + t++; + } + if (strlen(h) > MAXTAG) + h[MAXTAG] = 0; + if (strcmp(s,h) == 0) return s; + return h; +} + +void +openwin(char *name, char *buttons, Win *twin, int wintype) +{ + char buf[80]; + + wnew(twin); + if (wintype == Dictwin) + sprint(buf,"%s",name); + else + if ((wintype == Entrywin) && (count > 1)) + sprint(buf,"%s/%s/%s/%d",name, dict, format(pattern), curindex+1); + else + sprint(buf,"%s/%s/%s",name, dict, format(pattern)); + wname(twin, buf); + wtagwrite(twin, buttons, strlen(buttons)); + wclean(twin); + wdormant(twin); + if (wintype == Dictwin) + dispdicts(twin); + if (wintype == Matchwin) { + Mopen = True; + dispmatches(twin); + } + if (wintype == Entrywin) { + Eopen = True; + dispentry(twin); + } + handle(twin, wintype); +} + +void +vopenwin(void *v) +{ + void **arg; + char *name, *buttons; + Win *twin; + int wintype; + + arg = v; + name = arg[0]; + buttons = arg[1]; + twin = arg[2]; + wintype = (int)arg[3]; + sendul(arg[4], 0); + + openwin(name, buttons, twin, wintype); + threadexits(nil); +} + +void +procopenwin(char *name, char *buttons, Win *twin, int wintype) +{ + void *arg[5]; + Channel *c; + + c = chancreate(sizeof(ulong), 0); + arg[0] = name; + arg[1] = buttons; + arg[2] = twin; + arg[3] = (void*)wintype; + arg[4] = c; + proccreate(vopenwin, arg, 8192); + recvul(c); + chanfree(c); +} + +void +rexec(void *v) +{ + void **arg; + char *prog; + char **args; + int *fd; + Channel *c; + + arg = v; + prog = arg[0]; + args = arg[1]; + fd = arg[2]; + c = arg[3]; + + rfork(RFENVG|RFFDG); + dup(fd[1], 1); + close(fd[1]); + close(fd[0]); + procexec(c, prog, args); + threadprint(2, "Remote pipe execution failed: %s %r\n", prog); +abort(); + threadexits(nil); +} + +void +pexec(void *v) +{ + void **arg; + char *prog; + char **args; + Channel *c; + + arg = v; + prog = arg[0]; + args = arg[1]; + c = arg[2]; + + procexec(c, prog, args); + threadprint(2, "Remote execution failed: %s %r\n", prog); +abort(); + threadexits(nil); +} + +void +procpexec(char *prog, char **args) +{ + void *rexarg[4]; + Channel *c; + + c = chancreate(sizeof(ulong), 0); + rexarg[0] = prog; + rexarg[1] = args; + rexarg[2] = c; + + proccreate(pexec, rexarg, 8192); + recvul(c); + chanfree(c); +} + +void +kill(void) +{ + /* Kill all processes related to this one. */ + int fd; + + sprint(buffer, "/proc/%d/notepg", getpid()); + fd = open(buffer, OWRITE); + rfork(RFNOTEG); + write(fd, "kill", 4); +} + +int +command(char *com, Win *w, int wintype) +{ + char *buf; + + if (strncmp(com, "Del", 3) == 0) { + switch(wintype){ + case Entrywin: + if (wdel(w)) { + Eopen = False; + threadexits(nil); + } + break; + case Dictwin: + if (wdel(w)) + threadexits(nil); + break; + case Matchwin: + kill(); + if (Eopen) + if (~wdel(&Ewin)) /* Remove the entry window */ + wdel(&Ewin); + if (!wdel(w)) + wdel(w); + threadexits(nil); + break; + } + return True; + } + if (strncmp(com, "Next", 4) == 0){ + if (curone != nil) { + curone = chgaddr(1); + buf = getpattern(curone); + sprint(buffer,"%s/%s/%s", prog, dict, format(buf)); + wname(w, buffer); + dispentry(w); + } + return True; + } + if (strncmp(com, "Prev",4) == 0){ + if (curone != nil) { + curone = chgaddr(-1); + buf = getpattern(curone); + sprint(buffer,"%s/%s/%s", prog, dict, format(buf)); + wname(w, buffer); + dispentry(w); + } + return True; + } + if (strncmp(com, "Nmatch",6) == 0){ + if (curaddr[++curindex] == nil) + curindex = 0; + curone = curaddr[curindex]; + if (curone != nil) { + sprint(buffer,"%s/%s/%s/%d",prog,dict,format(pattern),curindex+1); + wname(w, buffer); + dispentry(w); + } + return True; + } + return False; +} + +void +handle(Win *w, int wintype) +{ + Event e, e2, ea, etoss; + char *s, *t, buf[80]; + int tmp, na; + + while (True) { + wevent(w, &e); + switch(e.c2){ + default: + /* threadprint(2,"unknown message %c%c\n", e.c1, e.c2); */ + break; + case 'i': + /* threadprint(2,"'%s' inserted in tag at %d\n", e.b, e.q0);*/ + break; + case 'I': + /* threadprint(2,"'%s' inserted in body at %d\n", e.b, e.q0);*/ + break; + case 'd': + /* threadprint(2, "'%s' deleted in tag at %d\n", e.b, e.q0);*/ + break; + case 'D': + /* threadprint(2, "'%s' deleted in body at %d\n", e.b, e.q0);*/ + break; + case 'x': + case 'X': /* Execute command. */ + if (e.flag & 2) + wevent(w, &e2); + if(e.flag & 8){ + wevent(w, &ea); + wevent(w, &etoss); + na = ea.nb; + } else + na = 0; + s = e.b; + if ((e.flag & 2) && e.nb == 0) + s = e2.b; + if(na){ + t = malloc(strlen(s)+1+na+1); + snprint(t, strlen(s)+1+na+1, "%s %s", s, ea.b); + s = t; + } + /* if it's a long message, it can't be for us anyway */ + if(!command(s, w, wintype)) /* send it back */ + wwriteevent(w, &e); + if(na) + free(s); + break; + case 'l': + case 'L': /* Look for something. */ + if (e.flag & 2) + wevent(w, &e); + wclean(w); /* Set clean bit. */ + if (wintype == Dictwin) { + strcpy(buf, e.b); + args[0] = lprog; + args[1] = "-d"; + args[2] = buf; + args[3] = nil; + procpexec(lprog, args); /* New adict with chosen dict. */ + } + if (wintype == Entrywin) { + strcpy(buf, e.b); + args[0] = lprog; + args[1] = "-d"; + args[2] = dict; + args[3] = buf; + args[4] = nil; + procpexec(lprog, args); /* New adict with chosen pattern. */ + } + if (wintype == Matchwin) { + tmp = atoi(e.b) - 1; + if ((tmp >= 0) && (tmp < MAXMATCH) && (curaddr[tmp] != nil)) { + curindex = tmp; + curone = curaddr[curindex]; + /* Display selected match. */ + if (Eopen) { + sprint(buf,"%s/%s/%s/%d",prog,dict,format(pattern),curindex+1); + wname(&Ewin, buf); + dispentry(&Ewin); + } + else + procopenwin(prog,"Nmatch Prev Next", &Ewin, Entrywin); + } + } + break; + } + } +} diff --git a/acme/bin/source/adict/_win.c b/acme/bin/source/adict/_win.c new file mode 100644 index 000000000..18657c572 --- /dev/null +++ b/acme/bin/source/adict/_win.c @@ -0,0 +1,315 @@ +#include +#include +#include +#include +#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; iaddr < 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; inr; i++){ + e->r[i] = geter(w, e->b+e->nb, &nb); + e->nb += nb; + } + e->r[e->nr] = 0; + e->b[e->nb] = 0; + if(getec(w) != '\n') + threadprint(2, "wevent: event syntax 2"); +} + +void +wslave(Win *w, Channel *ce) +{ + Event e; + + while(recv(ce, &e) >= 0) + wevent(w, &e); +} + +void +wwriteevent(Win *w, Event *e) +{ + threadprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1); +} + +int +wreadall(Win *w, char **sp) +{ + char *s; + int m, na, n; + + if(w->body != nil) + Bterm(w->body); + openbody(w, OREAD); + s = nil; + na = 0; + n = 0; + for(;;){ + if(na < n+512){ + na += 1024; + s = erealloc(s, na+1); + } + m = Bread(w->body, s+n, na-n); + if(m <= 0) + break; + n += m; + } + s[n] = 0; + Bterm(w->body); + w->body = nil; + *sp = s; + return n; +} diff --git a/acme/bin/source/adict/adict.c b/acme/bin/source/adict/adict.c new file mode 100644 index 000000000..7ce53e5c2 --- /dev/null +++ b/acme/bin/source/adict/adict.c @@ -0,0 +1,591 @@ +#include +#include +#include +#include +#include "win.h" +#include "adict.h" + +enum +{ + STACK = 8192, +}; + +char *prog = "adict"; +char *lprog = "/bin/adict"; +char *xprog = "/bin/dict"; +char *dict, *pattern, *curaddr[MAXMATCH], *curone, *args[6], buffer[80]; +char abuffer[80], fbuffer[80], pbuffer[80]; +int curindex, count, Eopen, Mopen; +Win Mwin, Ewin, Dwin; + +void openwin(char*, char*, Win*, int); +void handle(Win*, int); +void rexec(void*); +void pexec(void*); +int getaddr(char*); + +void +usage(void) +{ + fprint(2, "usage: %s [-d dictname] [pattern]\n", argv0); + threadexitsall(nil); +} + +int mainstacksize = STACK; + +void +threadmain(int argc, char** argv) +{ + ARGBEGIN{ + case 'd': + dict = strdup(ARGF()); + break; + default: + usage(); + }ARGEND + + /* if running as other name, note that fact */ + if(access(argv0, AEXIST) == 0) + lprog = argv0; + + switch(argc){ + case 1: + pattern = pbuffer; + strcpy(pattern,argv[0]); + if(dict == nil) + dict = "pgw"; + break; + case 0: + break; + default: + usage(); + } + + if ((dict == nil) && (pattern == nil)) + openwin(prog,"", &Dwin, Dictwin); + if (pattern == nil) + openwin(prog,"",&Ewin, Entrywin); + if ((count = getaddr(pattern)) <= 1) + openwin(prog,"Prev Next", &Ewin, Entrywin); + else + openwin(prog, "", &Mwin, Matchwin); +} + +static int +procrexec(char *xprog, ...) +{ + int fpipe[2]; + void *rexarg[4]; + Channel *c; + va_list va; + int i; + char *p; + + pipe(fpipe); + va_start(va, xprog); + p = xprog; + for(i=0; p && i+1 80) + fprint(2, "Error in getting addres from dict.\n"); + else { + t = pbuffer; + /* remove trailing whitespace, newline */ + if (t != nil){ + while(*t != 0 && *t != '\n') + t++; + if(t == 0 && t > pbuffer) + t--; + while(t >= pbuffer && (*t==' ' || *t=='\n' || *t=='\t' || *t=='\r')) + *t-- = 0; + } + res = pbuffer; + } + close(fd); + return(res); +} + +char* +chgaddr(int dir) +{ + /* Increment or decrement the current address (curone). */ + + int fd; + char *res, *t; + + res = nil; + if (dir < 0) + sprint(buffer,"%s-a", curone); + else + sprint(buffer,"%s+a", curone); + fd = procrexec(xprog, "-d", dict, "-c", buffer, nil); + if (read(fd, abuffer, 80) > 80) + fprint(2, "Error in getting addres from dict.\n"); + else { + res = abuffer; + while (*res != '#') res++; + t = res; + while ((*t != '\n') && (t != nil)) t++; + if (t != nil) *t = 0; + } + close(fd); + return(res); +} + +void +dispdicts(Win *cwin) +{ + /* Display available dictionaries in window. */ + + int fd, nb, i; + char buf[1024], *t; + + fd = procrexec(xprog, "-d", "?", nil); + wreplace(cwin, "0,$","",0); /* Clear window */ + while ((nb = read(fd, buf, 1024)) > 0) { + t = buf; + i = 0; + if (strncmp("Usage", buf, 5) == 0) { /* Remove first line. */ + while (t[0] != '\n') { + t++; + i++; + } + t++; + i++; + } + wwritebody(cwin, t, nb-i); + } + close(fd); + wclean(cwin); +} + +void +dispentry(Win *cwin) +{ + /* Display the current selection in window. */ + + int fd, nb; + char buf[BUFSIZE]; + + if (curone == nil) { + if (pattern != nil) { + sprint(buf,"Pattern not found.\n"); + wwritebody(cwin, buf, 19); + wclean(cwin); + } + return; + } + sprint(buffer,"%sp", curone); + fd = procrexec(xprog, "-d", dict, "-c", buffer, nil); + wreplace(cwin, "0,$","",0); /* Clear window */ + while ((nb = read(fd, buf, BUFSIZE)) > 0) { + wwritebody(cwin, buf, nb); + } + close(fd); + wclean(cwin); +} + +void +dispmatches(Win *cwin) +{ + /* Display the current matches. */ + + int fd, nb; + char buf[BUFSIZE]; + + sprint(buffer,"/%s/H", pattern); + fd = procrexec(xprog, "-d", dict, "-c", buffer, nil); + while ((nb = read(fd, buf, BUFSIZE)) > 0) + wwritebody(cwin, buf, nb); + close(fd); + wclean(cwin); +} + +char* +format(char *s) +{ + /* Format a string to be written in window tag. Acme doesn't like */ + /* non alpha-num's in the tag line. */ + + char *t, *h; + + t = fbuffer; + if (s == nil) { + *t = 0; + return t; + } + strcpy(t, s); + h = t; + while (*t != 0) { + if (!(((*t >= 'a') && (*t <= 'z')) || + ((*t >= 'A') && (*t <= 'Z')) || + ((*t >= '0') && (*t <= '9')))) + *t = '_'; + t++; + } + if (strlen(h) > MAXTAG) + h[MAXTAG] = 0; + if (strcmp(s,h) == 0) return s; + return h; +} + +void +openwin(char *name, char *buttons, Win *twin, int wintype) +{ + char buf[80]; + + wnew(twin); + if (wintype == Dictwin) + sprint(buf,"%s",name); + else + if ((wintype == Entrywin) && (count > 1)) + sprint(buf,"%s/%s/%s/%d",name, dict, format(pattern), curindex+1); + else + sprint(buf,"%s/%s/%s",name, dict, format(pattern)); + wname(twin, buf); + wtagwrite(twin, buttons, strlen(buttons)); + wclean(twin); + wdormant(twin); + if (wintype == Dictwin) + dispdicts(twin); + if (wintype == Matchwin) { + Mopen = True; + dispmatches(twin); + } + if (wintype == Entrywin) { + Eopen = True; + dispentry(twin); + } + handle(twin, wintype); +} + +void +vopenwin(void *v) +{ + void **arg; + char *name, *buttons; + Win *twin; + int wintype; + + arg = v; + name = arg[0]; + buttons = arg[1]; + twin = arg[2]; + wintype = (int)arg[3]; + sendul(arg[4], 0); + + openwin(name, buttons, twin, wintype); + threadexits(nil); +} + +void +procopenwin(char *name, char *buttons, Win *twin, int wintype) +{ + void *arg[5]; + Channel *c; + + c = chancreate(sizeof(ulong), 0); + arg[0] = name; + arg[1] = buttons; + arg[2] = twin; + arg[3] = (void*)wintype; + arg[4] = c; + proccreate(vopenwin, arg, STACK); + recvul(c); + chanfree(c); +} + +void +rexec(void *v) +{ + void **arg; + char *prog; + char **args; + int *fd; + Channel *c; + + arg = v; + prog = arg[0]; + args = arg[1]; + fd = arg[2]; + c = arg[3]; + + rfork(RFENVG|RFFDG); + dup(fd[1], 1); + close(fd[1]); + close(fd[0]); + procexec(c, prog, args); + fprint(2, "Remote pipe execution failed: %s %r\n", prog); +abort(); + threadexits(nil); +} + +void +pexec(void *v) +{ + void **arg; + char *prog; + char **args; + Channel *c; + + arg = v; + prog = arg[0]; + args = arg[1]; + c = arg[2]; + + procexec(c, prog, args); + fprint(2, "Remote execution failed: %s %r\n", prog); +abort(); + threadexits(nil); +} + +void +procpexec(char *prog, char **args) +{ + void *rexarg[4]; + Channel *c; + + c = chancreate(sizeof(ulong), 0); + rexarg[0] = prog; + rexarg[1] = args; + rexarg[2] = c; + + proccreate(pexec, rexarg, STACK); + recvul(c); + chanfree(c); +} + +void +kill(void) +{ + /* Kill all processes related to this one. */ + int fd; + + sprint(buffer, "/proc/%d/notepg", getpid()); + fd = open(buffer, OWRITE); + rfork(RFNOTEG); + write(fd, "kill", 4); +} + +int +command(char *com, Win *w, int wintype) +{ + char *buf; + + if (strncmp(com, "Del", 3) == 0) { + switch(wintype){ + case Entrywin: + if (wdel(w)) { + Eopen = False; + threadexits(nil); + } + break; + case Dictwin: + if (wdel(w)) + threadexits(nil); + break; + case Matchwin: + kill(); + if (Eopen) + if (~wdel(&Ewin)) /* Remove the entry window */ + wdel(&Ewin); + if (!wdel(w)) + wdel(w); + threadexits(nil); + break; + } + return True; + } + if (strncmp(com, "Next", 4) == 0){ + if (curone != nil) { + curone = chgaddr(1); + buf = getpattern(curone); + sprint(buffer,"%s/%s/%s", prog, dict, format(buf)); + wname(w, buffer); + dispentry(w); + } + return True; + } + if (strncmp(com, "Prev",4) == 0){ + if (curone != nil) { + curone = chgaddr(-1); + buf = getpattern(curone); + sprint(buffer,"%s/%s/%s", prog, dict, format(buf)); + wname(w, buffer); + dispentry(w); + } + return True; + } + if (strncmp(com, "Nmatch",6) == 0){ + if (curaddr[++curindex] == nil) + curindex = 0; + curone = curaddr[curindex]; + if (curone != nil) { + sprint(buffer,"%s/%s/%s/%d",prog,dict,format(pattern),curindex+1); + wname(w, buffer); + dispentry(w); + } + return True; + } + return False; +} + +void +handle(Win *w, int wintype) +{ + Event e, e2, ea, etoss; + char *s, *t, buf[80]; + int tmp, na; + + while (True) { + wevent(w, &e); + switch(e.c2){ + default: + /* fprint(2,"unknown message %c%c\n", e.c1, e.c2); */ + break; + case 'i': + /* fprint(2,"'%s' inserted in tag at %d\n", e.b, e.q0);*/ + break; + case 'I': + /* fprint(2,"'%s' inserted in body at %d\n", e.b, e.q0);*/ + break; + case 'd': + /* fprint(2, "'%s' deleted in tag at %d\n", e.b, e.q0);*/ + break; + case 'D': + /* fprint(2, "'%s' deleted in body at %d\n", e.b, e.q0);*/ + break; + case 'x': + case 'X': /* Execute command. */ + if (e.flag & 2) + wevent(w, &e2); + if(e.flag & 8){ + wevent(w, &ea); + wevent(w, &etoss); + na = ea.nb; + } else + na = 0; + s = e.b; + if ((e.flag & 2) && e.nb == 0) + s = e2.b; + if(na){ + t = malloc(strlen(s)+1+na+1); + snprint(t, strlen(s)+1+na+1, "%s %s", s, ea.b); + s = t; + } + /* if it's a long message, it can't be for us anyway */ + if(!command(s, w, wintype)) /* send it back */ + wwriteevent(w, &e); + if(na) + free(s); + break; + case 'l': + case 'L': /* Look for something. */ + if (e.flag & 2) + wevent(w, &e); + wclean(w); /* Set clean bit. */ + if (wintype == Dictwin) { + strcpy(buf, e.b); + args[0] = lprog; + args[1] = "-d"; + args[2] = buf; + args[3] = nil; + procpexec(lprog, args); /* New adict with chosen dict. */ + } + if (wintype == Entrywin) { + strcpy(buf, e.b); + args[0] = lprog; + args[1] = "-d"; + args[2] = dict; + args[3] = buf; + args[4] = nil; + procpexec(lprog, args); /* New adict with chosen pattern. */ + } + if (wintype == Matchwin) { + tmp = atoi(e.b) - 1; + if ((tmp >= 0) && (tmp < MAXMATCH) && (curaddr[tmp] != nil)) { + curindex = tmp; + curone = curaddr[curindex]; + /* Display selected match. */ + if (Eopen) { + sprint(buf,"%s/%s/%s/%d",prog,dict,format(pattern),curindex+1); + wname(&Ewin, buf); + dispentry(&Ewin); + } + else + procopenwin(prog,"Nmatch Prev Next", &Ewin, Entrywin); + } + } + break; + } + } +} diff --git a/acme/bin/source/adict/adict.h b/acme/bin/source/adict/adict.h new file mode 100644 index 000000000..fffaa5454 --- /dev/null +++ b/acme/bin/source/adict/adict.h @@ -0,0 +1,10 @@ +enum +{ + Matchwin, + Entrywin, + Dictwin +}; + +#define MAXTAG 20 +#define MAXMATCH 100 +#define BUFSIZE 4096 diff --git a/acme/bin/source/adict/man b/acme/bin/source/adict/man new file mode 100644 index 000000000..ad15bf079 --- /dev/null +++ b/acme/bin/source/adict/man @@ -0,0 +1,26 @@ +adict [-d dictionary] [pattern] + + adict with no arguments opens a window that displays all the currently +available dictionaries. To select a dictionary, click the right mouse button on +its name. + +-d dictionary Opens a window that interfaces to the specified dictionary. To + look up a word, enter it in the window, and click the right mouse button on it. + +[pattern] If no dictionary is specified, adict looks up the pattern in "oed" (Oxford + English Dictionary). If more than one entry is found, adict opens a window + displaying the headers of the matching entries. To display a particular entry + click the right mouse button on the number to its left. + +Quit Exit and remove all windows associated with this one. + +Nmatch Display the next matching entry. + +Next Display the next entry in the dictionary. + +Prev Display the previous entry in the dictionary. + + Nmatch works independently of Prev and Next. + + Any word in the window displaying an entry can be looked up in the selected +dictionary by clicking the right mouse button on that word. \ No newline at end of file diff --git a/acme/bin/source/adict/mkfile b/acme/bin/source/adict/mkfile new file mode 100644 index 000000000..14a079874 --- /dev/null +++ b/acme/bin/source/adict/mkfile @@ -0,0 +1,11 @@ + +#include +#include +#include +#include "win.h" + +void* +erealloc(void *p, uint n) +{ + p = realloc(p, n); + if(p == nil) + fprint(2, "realloc failed: %r"); + return p; +} + +void +wnew(Win *w) +{ + char buf[12]; + + w->ctl = open("/mnt/acme/new/ctl", ORDWR); + if(w->ctl<0 || read(w->ctl, buf, 12)!=12) + fprint (2, "can't open window ctl file: %r"); + ctlwrite(w, "noscroll\n"); + w->winid = atoi(buf); + w->event = openfile(w, "event"); + w->addr = -1; /* will be opened when needed */ + w->body = nil; + w->data = -1; +} + +int +openfile(Win *w, char *f) +{ + char buf[64]; + int fd; + + sprint(buf, "/mnt/acme/%d/%s", w->winid, f); + fd = open(buf, ORDWR|OCEXEC); + if(fd < 0) + fprint (2,"can't open window %s file: %r", f); + return fd; +} + +void +openbody(Win *w, int mode) +{ + char buf[64]; + + sprint(buf, "/mnt/acme/%d/body", w->winid); + w->body = Bopen(buf, mode|OCEXEC); + if(w->body == nil) + fprint(2,"can't open window body file: %r"); +} + +void +wwritebody(Win *w, char *s, int n) +{ + if(w->body == nil) + openbody(w, OWRITE); + if(Bwrite(w->body, s, n) != n) + fprint(2,"write error to window: %r"); + Bflush(w->body); +} + +void +wreplace(Win *w, char *addr, char *repl, int nrepl) +{ + if(w->addr < 0) + w->addr = openfile(w, "addr"); + if(w->data < 0) + w->data = openfile(w, "data"); + if(write(w->addr, addr, strlen(addr)) < 0){ + fprint(2, "mail: warning: badd address %s:%r\n", addr); + return; + } + if(write(w->data, repl, nrepl) != nrepl) + fprint(2, "writing data: %r"); +} + +static int +nrunes(char *s, int nb) +{ + int i, n; + Rune r; + + n = 0; + for(i=0; iaddr < 0) + w->addr = openfile(w, "addr"); + if(w->data < 0) + w->data = openfile(w, "data"); + m = q0; + while(m < q1){ + n = sprint(buf, "#%d", m); + if(write(w->addr, buf, n) != n) + fprint(2,"writing addr: %r"); + n = read(w->data, buf, sizeof buf); + if(n <= 0) + fprint(2,"reading data: %r"); + nr = nrunes(buf, n); + while(m+nr >q1){ + do; while(n>0 && (buf[--n]&0xC0)==0x80); + --nr; + } + if(n == 0) + break; + memmove(data, buf, n); + data += n; + *data = 0; + m += nr; + } +} + +void +wselect(Win *w, char *addr) +{ + if(w->addr < 0) + w->addr = openfile(w, "addr"); + if(write(w->addr, addr, strlen(addr)) < 0) + fprint(2,"writing addr"); + ctlwrite(w, "dot=addr\n"); +} + +void +wtagwrite(Win *w, char *s, int n) +{ + int fd; + + fd = openfile(w, "tag"); + if(write(fd, s, n) != n) + fprint(2,"tag write: %r"); + close(fd); +} + +void +ctlwrite(Win *w, char *s) +{ + int n; + + n = strlen(s); + if(write(w->ctl, s, n) != n) + fprint(2,"write error to ctl file: %r"); +} + +int +wdel(Win *w) +{ + if(write(w->ctl, "del\n", 4) != 4) + return False; + wdormant(w); + close(w->ctl); + w->ctl = -1; + close(w->event); + w->event = -1; + return True; +} + +void +wname(Win *w, char *s) +{ + char buf[128]; + + sprint(buf, "name %s\n", s); + ctlwrite(w, buf); +} + +void +wclean(Win *w) +{ + if(w->body) + Bflush(w->body); + ctlwrite(w, "clean\n"); +} + +void +wdormant(Win *w) +{ + if(w->addr >= 0){ + close(w->addr); + w->addr = -1; + } + if(w->body != nil){ + Bterm(w->body); + w->body = nil; + } + if(w->data >= 0){ + close(w->data); + w->data = -1; + } +} + +int +getec(Win *w) +{ + if(w->nbuf == 0){ + w->nbuf = read(w->event, w->buf, sizeof w->buf); + if(w->nbuf <= 0) + fprint(2,"event read error: %r"); + w->bufp = w->buf; + } + w->nbuf--; + return *w->bufp++; +} + +int +geten(Win *w) +{ + int n, c; + + n = 0; + while('0'<=(c=getec(w)) && c<='9') + n = n*10+(c-'0'); + if(c != ' ') + fprint(2, "event number syntax"); + return n; +} + +int +geter(Win *w, char *buf, int *nb) +{ + Rune r; + int n; + + r = getec(w); + buf[0] = r; + n = 1; + if(r < Runeself) + goto Return; + while(!fullrune(buf, n)) + buf[n++] = getec(w); + chartorune(&r, buf); + Return: + *nb = n; + return r; +} + + +void +wevent(Win *w, Event *e) +{ + int i, nb; + + e->c1 = getec(w); + e->c2 = getec(w); + e->q0 = geten(w); + e->q1 = geten(w); + e->flag = geten(w); + e->nr = geten(w); + if(e->nr > EVENTSIZE) + fprint(2, "wevent: event string too long"); + e->nb = 0; + for(i=0; inr; i++){ + e->r[i] = geter(w, e->b+e->nb, &nb); + e->nb += nb; + } + e->r[e->nr] = 0; + e->b[e->nb] = 0; + if(getec(w) != '\n') + fprint(2, "wevent: event syntax 2"); +} + +void +wslave(Win *w, Channel *ce) +{ + Event e; + + while(recv(ce, &e) >= 0) + wevent(w, &e); +} + +void +wwriteevent(Win *w, Event *e) +{ + fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1); +} + +int +wreadall(Win *w, char **sp) +{ + char *s; + int m, na, n; + + if(w->body != nil) + Bterm(w->body); + openbody(w, OREAD); + s = nil; + na = 0; + n = 0; + for(;;){ + if(na < n+512){ + na += 1024; + s = erealloc(s, na+1); + } + m = Bread(w->body, s+n, na-n); + if(m <= 0) + break; + n += m; + } + s[n] = 0; + Bterm(w->body); + w->body = nil; + *sp = s; + return n; +} diff --git a/acme/bin/source/adict/win.h b/acme/bin/source/adict/win.h new file mode 100644 index 000000000..8e1698aa1 --- /dev/null +++ b/acme/bin/source/adict/win.h @@ -0,0 +1,59 @@ +enum +{ + False, + True, + EVENTSIZE=256, +}; + + +typedef struct Event Event; +struct Event +{ + int c1; + int c2; + int q0; + int q1; + int flag; + int nb; + int nr; + char b[EVENTSIZE*UTFmax+1]; + Rune r[EVENTSIZE+1]; +}; + + +typedef struct Win Win; +struct Win +{ + int winid; + int addr; + Biobuf *body; + int ctl; + int data; + int event; + char buf[512]; + char *bufp; + int nbuf; +}; + +int dead(Win*); +void wnew(Win*); +void wwritebody(Win*, char *s, int n); +void wread(Win*, uint, uint, char*); +void wclean(Win*); +void wname(Win*, char*); +void wdormant(Win*); +void wevent(Win*, Event*); +void wtagwrite(Win*, char*, int); +void wwriteevent(Win*, Event*); +void wslave(Win*, Channel*); /* chan(Event) */ +void wreplace(Win*, char*, char*, int); +void wselect(Win*, char*); +int wdel(Win*); +int wreadall(Win*, char**); + +void ctlwrite(Win*, char*); +int getec(Win*); +int geten(Win*); +int geter(Win*, char*, int*); +int openfile(Win*, char*); +void openbody(Win*, int); diff --git a/acme/bin/source/mkfile b/acme/bin/source/mkfile new file mode 100644 index 000000000..48146338b --- /dev/null +++ b/acme/bin/source/mkfile @@ -0,0 +1,27 @@ + +#include + +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 +#include +#include +#include + +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' '; c++){ + n++; + s++; + } + inword = 0; + w = s; + t = s; + do{ + c = *(uchar*)t; + if(c < Runeself) + wid = 1; + else{ + wid = chartorune(&r, t); + c = r; + } + wordchar = 0; + if(isalpha(c)) + wordchar = 1; + if(inword && !wordchar){ + if(c=='\'' && isalpha(t[1])) + goto Continue; + m = t-w; + if(m > 1){ + memmove(buf, w, m); + buf[m] = 0; + Bprint(&bout, "%s:#%d,#%d:%s\n", name, wn, n, buf); + } + inword = 0; + }else if(!inword && wordchar){ + wn = n; + w = t; + inword = 1; + } + if(c=='\\' && (isalpha(t[1]) || t[1]=='(')){ + switch(t[1]){ + case '(': + m = 4; + break; + case 'f': + if(t[2] == '(') + m = 5; + else + m = 3; + break; + case 's': + if(t[2] == '+' || t[2]=='-'){ + if(t[3] == '(') + m = 6; + else + m = 4; + }else{ + if(t[2] == '(') + m = 5; + else if(t[2]=='1' || t[2]=='2' || t[2]=='3') + m = 4; + else + m = 3; + } + break; + default: + m = 2; + } + while(m-- > 0){ + if(*t == '\n') + break; + n++; + t++; + } + continue; + } + Continue: + n++; + t += wid; + }while(c != '\n'); + } + Bterm(&b); +} diff --git a/acme/bin/source/win/_fs.c b/acme/bin/source/win/_fs.c new file mode 100644 index 000000000..6791a21f2 --- /dev/null +++ b/acme/bin/source/win/_fs.c @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#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; ic1 = '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; ib[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; ib+i, nb-i); i+=wid,j++) + wid = chartorune(&rune, ep->b+i); + memmove(tmp, ep->b+i, nb-i); + partial = nb-i; + ep->nb = i; + ep->nr = j; + ep->b[i] = '\0'; + if(i != 0){ + sendp(win->cevent, ep); + recvp(writechan); + } + partial = nb-i; + memmove(e[n]->b, tmp, partial); + r->ofcall.count = r->ifcall.count; + respond(r, nil); +} + +void +fsdestroyfid(Fid *fid) +{ + if(fid->aux) + free(fid->aux); +} + +Srv fs = { +.read= fsread, +.write= fswrite, +.flush= fsflush, +.destroyfid= fsdestroyfid, +.leavefdsopen= 1, +}; + +void +mountcons(void) +{ + fschan = chancreate(sizeof(Fsevent), 0); + writechan = chancreate(sizeof(void*), 0); + fs.tree = alloctree("win", "win", DMDIR|0555, nil); + devcons = createfile(fs.tree->root, "cons", "win", 0666, nil); + if(devcons == nil) + sysfatal("creating /dev/cons: %r"); + devnew = createfile(fs.tree->root, "wnew", "win", 0666, nil); + if(devnew == nil) + sysfatal("creating /dev/wnew: %r"); + threadpostmountsrv(&fs, nil, "/dev", MBEFORE); +} diff --git a/acme/bin/source/win/_main.c b/acme/bin/source/win/_main.c new file mode 100644 index 000000000..4ac9c19d5 --- /dev/null +++ b/acme/bin/source/win/_main.c @@ -0,0 +1,651 @@ +#include +#include +#include +#include +#include +#include <9p.h> +#include +#include "dat.h" + +void mainctl(void*); +void startcmd(char *[], int*); +void stdout2body(void*); + +int debug; +int notepg; +int eraseinput; +int dirty = 0; + +Window *win; /* the main window */ + +void +usage(void) +{ + fprint(2, "usage: win [command]\n"); + threadexitsall("usage"); +} + +void +threadmain(int argc, char *argv[]) +{ + int i, j; + char *dir, *tag, *name; + char buf[1024], **av; + + quotefmtinstall(); + rfork(RFNAMEG); + ARGBEGIN{ + case 'd': + debug = 1; + chatty9p++; + break; + case 'e': + eraseinput = 1; + break; + case 'D': +{extern int _threaddebuglevel; + _threaddebuglevel = 1<<20; +} + }ARGEND + + if(argc == 0){ + av = emalloc(3*sizeof(char*)); + av[0] = "rc"; + av[1] = "-i"; + name = getenv("sysname"); + }else{ + av = argv; + name = utfrrune(av[0], '/'); + if(name) + name++; + else + name = av[0]; + } + + if(getwd(buf, sizeof buf) == 0) + dir = "/"; + else + dir = buf; + dir = estrdup(dir); + tag = estrdup(dir); + tag = eappend(estrdup(tag), "/-", name); + win = newwindow(); + snprint(buf, sizeof buf, "%d", win->id); + putenv("winid", buf); + winname(win, tag); + wintagwrite(win, "Send Noscroll", 5+8); + threadcreate(mainctl, win, STACK); + mountcons(); + threadcreate(fsloop, nil, STACK); + startpipe(); + startcmd(av, ¬epg); + + strcpy(buf, "win"); + j = 3; + for(i=0; ictl, "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 && sifcall.count > 0){ + partial = utfncpy(tmp, s, UTFmax); + assert(partial > 0); + chartorune(&rune, tmp); + partial = runelen(rune); + free(s); + n = 1; + goto Partial; + } + n = utfnlen(r->ofcall.data, nb); + if(nb==strlen(s) && eofchar) + n++; + r->ofcall.count = nb; + q = r->aux; + if(q == nil) + eq = &q; + r->aux = nil; + if(debug) + fprint(2, "read returns %lud-%lud: %.*q\n", q0, q0+n, n, r->ofcall.data); + respond(r, nil); + return n; +} + +static int +_sendinput(Window *w, ulong q0, ulong *q1) +{ + char buf[32]; + int n; + + n = __sendinput(w, q0, *q1); + if(!n || !eraseinput) + return n; + /* erase q0 to q0+n */ + sprint(buf, "#%lud,#%lud", q0, q0+n); + winsetaddr(w, buf, 0); + write(w->data, buf, 0); + *q1 -= n; + return 0; +} + +int +sendinput(Window *w, ulong q0, ulong *q1) +{ + ulong n; + Req *oq; + + n = 0; + do { + oq = q; + n += _sendinput(w, q0+n, q1); + } while(q != oq); + return n; +} + +Event esendinput; +void +fsloop(void*) +{ + Fsevent e; + Req **l, *r; + + eq = &q; + memset(&esendinput, 0, sizeof esendinput); + esendinput.c1 = 'C'; + for(;;){ + while(recv(fschan, &e) == -1) + ; + r = e.r; + switch(e.type){ + case 'r': + *eq = r; + r->aux = nil; + eq = &r->aux; + /* call sendinput with hostpt and endpt */ + sendp(win->cevent, &esendinput); + break; + case 'f': + for(l=&q; *l; l=&(*l)->aux){ + if(*l == r->oldreq){ + *l = (*l)->aux; + if(*l == nil) + eq = l; + respond(r->oldreq, "interrupted"); + break; + } + } + respond(r, nil); + break; + } + } +} + +void +sendit(char *s) +{ +// char tmp[32]; + + write(win->body, s, strlen(s)); +/* + * RSC: The problem here is that other procs can call sendit, + * so we lose our single-threadedness if we call sendinput. + * In fact, we don't even have the right queue memory, + * I think that we'll get a write event from the body write above, + * and we can do the sendinput then, from our single thread. + * + * I still need to figure out how to test this assertion for + * programs that use /srv/win* + * + winselect(win, "$", 0); + seek(win->addr, 0UL, 0); + if(read(win->addr, tmp, 2*12) == 2*12) + hostpt += sendinput(win, hostpt, atol(tmp), ); + */ +} + +void +execevent(Window *w, Event *e, int (*command)(Window*, char*)) +{ + Event *ea, *e2; + int n, na, len, needfree; + char *s, *t; + + ea = nil; + e2 = nil; + if(e->flag & 2) + e2 = recvp(w->cevent); + if(e->flag & 8){ + ea = recvp(w->cevent); + na = ea->nb; + recvp(w->cevent); + }else + na = 0; + + needfree = 0; + s = e->b; + if(e->nb==0 && (e->flag&2)){ + s = e2->b; + e->q0 = e2->q0; + e->q1 = e2->q1; + e->nb = e2->nb; + } + if(e->nb==0 && e->q0q1){ + /* 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; icevent); + if(debug) + fprint(2, "msg: %C %C %d %d %d %d %q\n", + e->c1 ? e->c1 : ' ', e->c2 ? e->c2 : ' ', e->q0, e->q1, e->flag, e->nb, e->b); + switch(e->c1){ + default: + Unknown: + fprint(2, "unknown message %c%c\n", e->c1, e->c2); + break; + + case 'C': /* input needed for /dev/cons */ + if(pendingS) + pendingK = 1; + else + hostpt += sendinput(w, hostpt, &endpt); + break; + + case 'S': /* output to stdout */ + sprint(tmp, "#%lud", hostpt); + winsetaddr(w, tmp, 0); + write(w->data, e->b, e->nb); + pendingS += utfnlen(e->b, e->nb); + break; + + case 'E': /* write to tag or body; body happens due to sendit */ + delta = e->q1-e->q0; + if(e->c2=='I'){ + endpt += delta; + if(e->q0 < hostpt) + hostpt += delta; + else + hostpt += sendinput(w, hostpt, &endpt); + break; + } + if(!islower(e->c2)) + fprint(2, "win msg: %C %C %d %d %d %d %q\n", + e->c1, e->c2, e->q0, e->q1, e->flag, e->nb, e->b); + break; + + case 'F': /* generated by our actions (specifically case 'S' above) */ + delta = e->q1-e->q0; + if(e->c2=='D'){ + /* we know about the delete by _sendinput */ + break; + } + if(e->c2=='I'){ + pendingS -= e->q1 - e->q0; + if(pendingS < 0) + fprint(2, "win: pendingS = %d\n", pendingS); + if(e->q0 != hostpt) + fprint(2, "win: insert at %d expected %lud\n", e->q0, hostpt); + endpt += delta; + hostpt += delta; + sendp(writechan, nil); + if(pendingS == 0 && pendingK){ + pendingK = 0; + hostpt += sendinput(w, hostpt, &endpt); + } + break; + } + if(!islower(e->c2)) + fprint(2, "win msg: %C %C %d %d %d %d %q\n", + e->c1, e->c2, e->q0, e->q1, e->flag, e->nb, e->b); + break; + + case 'K': + delta = e->q1-e->q0; + switch(e->c2){ + case 'D': + endpt -= delta; + if(e->q1 < hostpt) + hostpt -= delta; + else if(e->q0 < hostpt) + hostpt = e->q0; + break; + case 'I': + delta = e->q1 - e->q0; + endpt += delta; + if(endpt < e->q1) /* just in case */ + endpt = e->q1; + if(e->q0 < hostpt) + hostpt += delta; + if(e->nr>0 && e->r[e->nr-1]==0x7F){ + write(notepg, "interrupt", 9); + hostpt = endpt; + break; + } + if(e->q0 >= hostpt + && hasboundary(e->r, e->nr)){ + /* + * If we are between the S message (which + * we processed by inserting text in the + * window) and the F message notifying us + * that the text has been inserted, then our + * impression of the hostpt and acme's + * may be different. This could be seen if you + * hit enter a bunch of times in a con + * session. To work around the unreliability, + * only send input if we don't have an S pending. + * The same race occurs between when a character + * is typed and when we get notice of it, but + * since characters tend to be typed at the end + * of the buffer, we don't run into it. There's + * no workaround possible for this typing race, + * since we can't tell when the user has typed + * something but we just haven't been notified. + */ + if(pendingS) + pendingK = 1; + else + hostpt += sendinput(w, hostpt, &endpt); + } + break; + } + break; + + case 'M': /* mouse */ + delta = e->q1-e->q0; + switch(e->c2){ + case 'x': + case 'X': + execevent(w, e, command); + break; + + case 'l': /* reflect all searches back to acme */ + case 'L': + if(e->flag & 2) + recvp(w->cevent); + winwriteevent(w, e); + break; + + case 'I': + endpt += delta; + if(e->q0 < hostpt) + hostpt += delta; + else + hostpt += sendinput(w, hostpt, &endpt); + break; + + case 'D': + endpt -= delta; + if(e->q1 < hostpt) + hostpt -= delta; + else if(e->q0 < hostpt) + hostpt = e->q0; + break; + case 'd': /* modify away; we don't care */ + case 'i': + break; + + default: + goto Unknown; + } + } + } +} + +enum +{ + NARGS = 100, + NARGCHAR = 8*1024, + EXECSTACK = STACK+(NARGS+1)*sizeof(char*)+NARGCHAR +}; + +struct Exec +{ + char **argv; + Channel *cpid; +}; + +int +lookinbin(char *s) +{ + if(s[0] == '/') + return 0; + if(s[0]=='.' && s[1]=='/') + return 0; + if(s[0]=='.' && s[1]=='.' && s[2]=='/') + return 0; + return 1; +} + +/* adapted from mail. not entirely free of details from that environment */ +void +execproc(void *v) +{ + struct Exec *e; + char *cmd, **av; + Channel *cpid; + + e = v; + rfork(RFCFDG|RFNOTEG); + av = e->argv; + close(0); + open("/dev/cons", OREAD); + close(1); + open("/dev/cons", OWRITE); + dup(1, 2); + cpid = e->cpid; + free(e); + procexec(cpid, av[0], av); + if(lookinbin(av[0])){ + cmd = estrstrdup("/bin/", av[0]); + procexec(cpid, cmd, av); + } + error("can't exec %s: %r", av[0]); +} + +void +startcmd(char *argv[], int *notepg) +{ + struct Exec *e; + Channel *cpid; + char buf[64]; + int pid; + + e = emalloc(sizeof(struct Exec)); + e->argv = argv; + cpid = chancreate(sizeof(ulong), 0); + e->cpid = cpid; + sprint(buf, "/mnt/wsys/%d", win->id); + bind(buf, "/dev/acme", MREPL); + proccreate(execproc, e, EXECSTACK); + do + pid = recvul(cpid); + while(pid == -1); + sprint(buf, "/proc/%d/notepg", pid); + *notepg = open(buf, OWRITE); +} diff --git a/acme/bin/source/win/dat.h b/acme/bin/source/win/dat.h new file mode 100644 index 000000000..38d31dcf0 --- /dev/null +++ b/acme/bin/source/win/dat.h @@ -0,0 +1,95 @@ +typedef struct Fsevent Fsevent; +typedef struct Event Event; +typedef struct Message Message; +typedef struct Window Window; + +enum +{ + STACK = 8192, + NPIPEDATA = 8000, + NPIPE = NPIPEDATA+32, + /* EVENTSIZE is really 256 in acme, but we use events internally and want bigger buffers */ + EVENTSIZE = 8192, + NEVENT = 5, +}; + +struct Fsevent +{ + int type; + void *r; +}; + +struct Event +{ + int c1; + int c2; + int q0; + int q1; + int flag; + int nb; + int nr; + char b[EVENTSIZE*UTFmax+1]; + Rune r[EVENTSIZE+1]; +}; + +struct Window +{ + /* file descriptors */ + int ctl; + int event; + int addr; + int data; + int body; + + /* event input */ + char buf[512]; + char *bufp; + int nbuf; + Event e[NEVENT]; + + int id; + int open; + Channel *cevent; +}; + +extern Window* newwindow(void); +extern int winopenfile(Window*, char*); +extern void wintagwrite(Window*, char*, int); +extern void winname(Window*, char*); +extern void winwriteevent(Window*, Event*); +extern int winread(Window*, uint, uint, char*); +extern int windel(Window*, int); +extern void wingetevent(Window*, Event*); +extern void wineventproc(void*); +extern void winclean(Window*); +extern int winselect(Window*, char*, int); +extern int winsetaddr(Window*, char*, int); +extern void windormant(Window*); +extern void winsetdump(Window*, char*, char*); + +extern void ctlprint(int, char*, ...); +extern void* emalloc(uint); +extern char* estrdup(char*); +extern char* estrstrdup(char*, char*); +extern char* egrow(char*, char*, char*); +extern char* eappend(char*, char*, char*); +extern void error(char*, ...); + +extern void startpipe(void); +extern void sendit(char*); +extern void execevent(Window *w, Event *e, int (*)(Window*, char*)); + +extern void mountcons(void); +extern void fsloop(void*); + +extern int newpipewin(int, char*); +extern void startpipe(void); +extern int pipecommand(Window*, char*); +extern void pipectl(void*); + +#pragma varargck argpos error 1 +#pragma varargck argpos ctlprint 2 + +extern Window *win; +extern Channel *fschan, *writechan; + diff --git a/acme/bin/source/win/fs.c b/acme/bin/source/win/fs.c new file mode 100644 index 000000000..6c41eb072 --- /dev/null +++ b/acme/bin/source/win/fs.c @@ -0,0 +1,147 @@ +#include +#include +#include +#include +#include <9p.h> +#include "dat.h" + +Channel *fschan; +Channel *writechan; + +static File *devcons, *devnew; + +static void +fsread(Req *r) +{ + Fsevent e; + + if(r->fid->file == devnew){ + if(r->fid->aux==nil){ + respond(r, "phase error"); + return; + } + readstr(r, r->fid->aux); + respond(r, nil); + return; + } + + assert(r->fid->file == devcons); + e.type = 'r'; + e.r = r; + send(fschan, &e); +} + +static void +fsflush(Req *r) +{ + Fsevent e; + + e.type = 'f'; + e.r = r; + send(fschan, &e); +} + +static void +fswrite(Req *r) +{ + static Event *e[4]; + Event *ep; + int i, j, ei, nb, wid, pid; + Rune rune; + char *s; + char tmp[UTFmax], *t; + static int n, partial; + + if(r->fid->file == devnew){ + if(r->fid->aux){ + respond(r, "already created a window"); + return; + } + s = emalloc(r->ifcall.count+1); + memmove(s, r->ifcall.data, r->ifcall.count); + s[r->ifcall.count] = 0; + pid = strtol(s, &t, 0); + if(*t==' ') + t++; + i = newpipewin(pid, t); + free(s); + s = emalloc(32); + sprint(s, "%lud", (ulong)i); + r->fid->aux = s; + r->ofcall.count = r->ifcall.count; + respond(r, nil); + return; + } + + assert(r->fid->file == devcons); + + if(e[0] == nil){ + for(i=0; ic1 = '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; ib[i] != '\0') + t[j++] = ep->b[i]; + nb = j; + t[j] = '\0'; + } + ei = nb>8192? 8192 : nb; + /* process bytes into runes, transferring terminal partial runes into next buffer */ + for(i=j=0; ib+i, ei-i); i+=wid,j++) + wid = chartorune(&rune, ep->b+i); + memmove(tmp, ep->b+i, nb-i); + partial = nb-i; + ep->nb = i; + ep->nr = j; + ep->b[i] = '\0'; + if(i != 0){ + sendp(win->cevent, ep); + recvp(writechan); + } + partial = nb-i; + memmove(e[n]->b, tmp, partial); + r->ofcall.count = r->ifcall.count; + respond(r, nil); +} + +void +fsdestroyfid(Fid *fid) +{ + if(fid->aux) + free(fid->aux); +} + +Srv fs = { +.read= fsread, +.write= fswrite, +.flush= fsflush, +.destroyfid= fsdestroyfid, +.leavefdsopen= 1, +}; + +void +mountcons(void) +{ + fschan = chancreate(sizeof(Fsevent), 0); + writechan = chancreate(sizeof(void*), 0); + fs.tree = alloctree("win", "win", DMDIR|0555, nil); + devcons = createfile(fs.tree->root, "cons", "win", 0666, nil); + if(devcons == nil) + sysfatal("creating /dev/cons: %r"); + devnew = createfile(fs.tree->root, "wnew", "win", 0666, nil); + if(devnew == nil) + sysfatal("creating /dev/wnew: %r"); + threadpostmountsrv(&fs, nil, "/dev", MBEFORE); +} diff --git a/acme/bin/source/win/main.c b/acme/bin/source/win/main.c new file mode 100644 index 000000000..916f2b05f --- /dev/null +++ b/acme/bin/source/win/main.c @@ -0,0 +1,646 @@ +#include +#include +#include +#include +#include +#include <9p.h> +#include +#include "dat.h" + +void mainctl(void*); +void startcmd(char *[], int*); +void stdout2body(void*); + +int debug; +int notepg; +int eraseinput; +int dirty = 0; + +Window *win; /* the main window */ + +void +usage(void) +{ + fprint(2, "usage: win [command]\n"); + threadexitsall("usage"); +} + +void +threadmain(int argc, char *argv[]) +{ + int i, j; + char *dir, *tag, *name; + char buf[1024], **av; + + quotefmtinstall(); + rfork(RFNAMEG); + ARGBEGIN{ + case 'd': + debug = 1; + chatty9p++; + break; + case 'e': + eraseinput = 1; + break; + case 'D': +{extern int _threaddebuglevel; + _threaddebuglevel = 1<<20; +} + }ARGEND + + if(argc == 0){ + av = emalloc(3*sizeof(char*)); + av[0] = "rc"; + av[1] = "-i"; + name = getenv("sysname"); + }else{ + av = argv; + name = utfrrune(av[0], '/'); + if(name) + name++; + else + name = av[0]; + } + + if(getwd(buf, sizeof buf) == 0) + dir = "/"; + else + dir = buf; + dir = estrdup(dir); + tag = estrdup(dir); + tag = eappend(estrdup(tag), "/-", name); + win = newwindow(); + snprint(buf, sizeof buf, "%d", win->id); + putenv("winid", buf); + winname(win, tag); + wintagwrite(win, "Send Noscroll", 5+8); + threadcreate(mainctl, win, STACK); + mountcons(); + threadcreate(fsloop, nil, STACK); + startpipe(); + startcmd(av, ¬epg); + + strcpy(buf, "win"); + j = 3; + for(i=0; ictl, "scroll"); + winsetdump(win, dir, buf); +} + +int +EQUAL(char *s, char *t) +{ + while(tolower(*s) == tolower(*t++)) + if(*s++ == '\0') + return 1; + return 0; +} + +int +command(Window *w, char *s) +{ + while(*s==' ' || *s=='\t' || *s=='\n') + s++; + if(strcmp(s, "Delete")==0 || strcmp(s, "Del")==0){ + windel(w, 1); + threadexitsall(nil); + return 1; + } + if(EQUAL(s, "scroll")){ + ctlprint(w->ctl, "scroll\nshow"); + return 1; + } + if(EQUAL(s, "noscroll")){ + ctlprint(w->ctl, "noscroll"); + return 1; + } + return 0; +} + +static long +utfncpy(char *to, char *from, int n) +{ + char *end, *e; + + e = to+n; + if(to >= e) + return 0; + end = memccpy(to, from, '\0', e - to); + if(end == nil){ + end = e; + if(end[-1]&0x80){ + if(end-2>=to && (end[-2]&0xE0)==0xC0) + return end-to; + if(end-3>=to && (end[-3]&0xF0)==0xE0) + return end-to; + while(end>to && (*--end&0xC0)==0x80) + ; + } + }else + end--; + return end - to; +} + +/* sendinput and fsloop run in the same proc (can't interrupt each other). */ +static Req *q; +static Req **eq; +static int +__sendinput(Window *w, ulong q0, ulong q1) +{ + char *s, *t; + int n, nb, eofchar; + static int partial; + static char tmp[UTFmax]; + Req *r; + Rune rune; + + if(!q) + return 0; + + r = q; + n = 0; + if(partial){ + Partial: + nb = partial; + if(nb > r->ifcall.count) + nb = r->ifcall.count; + memmove(r->ofcall.data, tmp, nb); + if(nb!=partial) + memmove(tmp, tmp+nb, partial-nb); + partial -= nb; + q = r->aux; + if(q == nil) + eq = &q; + r->aux = nil; + r->ofcall.count = nb; + if(debug) + fprint(2, "satisfy read with partial\n"); + respond(r, nil); + return n; + } + if(q0==q1) + return 0; + s = emalloc((q1-q0)*UTFmax+1); + n = winread(w, q0, q1, s); + s[n] = '\0'; + t = strpbrk(s, "\n\004"); + if(t == nil){ + free(s); + return 0; + } + r = q; + eofchar = 0; + if(*t == '\004'){ + eofchar = 1; + *t = '\0'; + }else + *++t = '\0'; + nb = utfncpy((char*)r->ofcall.data, s, r->ifcall.count); + if(nb==0 && sifcall.count > 0){ + partial = utfncpy(tmp, s, UTFmax); + assert(partial > 0); + chartorune(&rune, tmp); + partial = runelen(rune); + free(s); + n = 1; + goto Partial; + } + n = utfnlen(r->ofcall.data, nb); + if(nb==strlen(s) && eofchar) + n++; + r->ofcall.count = nb; + q = r->aux; + if(q == nil) + eq = &q; + r->aux = nil; + if(debug) + fprint(2, "read returns %lud-%lud: %.*q\n", q0, q0+n, n, r->ofcall.data); + respond(r, nil); + return n; +} + +static int +_sendinput(Window *w, ulong q0, ulong *q1) +{ + char buf[32]; + int n; + + n = __sendinput(w, q0, *q1); + if(!n || !eraseinput) + return n; + /* erase q0 to q0+n */ + sprint(buf, "#%lud,#%lud", q0, q0+n); + winsetaddr(w, buf, 0); + write(w->data, buf, 0); + *q1 -= n; + return 0; +} + +int +sendinput(Window *w, ulong q0, ulong *q1) +{ + ulong n; + Req *oq; + + n = 0; + do { + oq = q; + n += _sendinput(w, q0+n, q1); + } while(q != oq); + return n; +} + +Event esendinput; +void +fsloop(void*) +{ + Fsevent e; + Req **l, *r; + + eq = &q; + memset(&esendinput, 0, sizeof esendinput); + esendinput.c1 = 'C'; + for(;;){ + while(recv(fschan, &e) == -1) + ; + r = e.r; + switch(e.type){ + case 'r': + *eq = r; + r->aux = nil; + eq = &r->aux; + /* call sendinput with hostpt and endpt */ + sendp(win->cevent, &esendinput); + break; + case 'f': + for(l=&q; *l; l=&(*l)->aux){ + if(*l == r->oldreq){ + *l = (*l)->aux; + if(*l == nil) + eq = l; + respond(r->oldreq, "interrupted"); + break; + } + } + respond(r, nil); + break; + } + } +} + +void +sendit(char *s) +{ +// char tmp[32]; + + write(win->body, s, strlen(s)); +/* + * RSC: The problem here is that other procs can call sendit, + * so we lose our single-threadedness if we call sendinput. + * In fact, we don't even have the right queue memory, + * I think that we'll get a write event from the body write above, + * and we can do the sendinput then, from our single thread. + * + * I still need to figure out how to test this assertion for + * programs that use /srv/win* + * + winselect(win, "$", 0); + seek(win->addr, 0UL, 0); + if(read(win->addr, tmp, 2*12) == 2*12) + hostpt += sendinput(win, hostpt, atol(tmp), ); + */ +} + +void +execevent(Window *w, Event *e, int (*command)(Window*, char*)) +{ + Event *ea, *e2; + int n, na, len, needfree; + char *s, *t; + + ea = nil; + e2 = nil; + if(e->flag & 2) + e2 = recvp(w->cevent); + if(e->flag & 8){ + ea = recvp(w->cevent); + na = ea->nb; + recvp(w->cevent); + }else + na = 0; + + needfree = 0; + s = e->b; + if(e->nb==0 && (e->flag&2)){ + s = e2->b; + e->q0 = e2->q0; + e->q1 = e2->q1; + e->nb = e2->nb; + } + if(e->nb==0 && e->q0q1){ + /* 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; icevent); + if(debug) + fprint(2, "msg: %C %C %d %d %d %d %q\n", + e->c1 ? e->c1 : ' ', e->c2 ? e->c2 : ' ', e->q0, e->q1, e->flag, e->nb, e->b); + switch(e->c1){ + default: + Unknown: + fprint(2, "unknown message %c%c\n", e->c1, e->c2); + break; + + case 'C': /* input needed for /dev/cons */ + if(pendingS) + pendingK = 1; + else + hostpt += sendinput(w, hostpt, &endpt); + break; + + case 'S': /* output to stdout */ + sprint(tmp, "#%lud", hostpt); + winsetaddr(w, tmp, 0); + write(w->data, e->b, e->nb); + pendingS += e->nr; + break; + + case 'E': /* write to tag or body; body happens due to sendit */ + delta = e->q1-e->q0; + if(e->c2=='I'){ + endpt += delta; + if(e->q0 < hostpt) + hostpt += delta; + else + hostpt += sendinput(w, hostpt, &endpt); + break; + } + if(!islower(e->c2)) + fprint(2, "win msg: %C %C %d %d %d %d %q\n", + e->c1, e->c2, e->q0, e->q1, e->flag, e->nb, e->b); + break; + + case 'F': /* generated by our actions (specifically case 'S' above) */ + delta = e->q1-e->q0; + if(e->c2=='D'){ + /* we know about the delete by _sendinput */ + break; + } + if(e->c2=='I'){ + pendingS -= e->q1 - e->q0; + if(pendingS < 0) + fprint(2, "win: pendingS = %d\n", pendingS); + if(e->q0 != hostpt) + fprint(2, "win: insert at %d expected %lud\n", e->q0, hostpt); + endpt += delta; + hostpt += delta; + sendp(writechan, nil); + if(pendingS == 0 && pendingK){ + pendingK = 0; + hostpt += sendinput(w, hostpt, &endpt); + } + break; + } + if(!islower(e->c2)) + fprint(2, "win msg: %C %C %d %d %d %d %q\n", + e->c1, e->c2, e->q0, e->q1, e->flag, e->nb, e->b); + break; + + case 'K': + delta = e->q1-e->q0; + switch(e->c2){ + case 'D': + endpt -= delta; + if(e->q1 < hostpt) + hostpt -= delta; + else if(e->q0 < hostpt) + hostpt = e->q0; + break; + case 'I': + delta = e->q1 - e->q0; + endpt += delta; + if(endpt < e->q1) /* just in case */ + endpt = e->q1; + if(e->q0 < hostpt) + hostpt += delta; + if(e->nr>0 && e->r[e->nr-1]==0x7F){ + write(notepg, "interrupt", 9); + hostpt = endpt; + break; + } + if(e->q0 >= hostpt + && hasboundary(e->r, e->nr)){ + /* + * If we are between the S message (which + * we processed by inserting text in the + * window) and the F message notifying us + * that the text has been inserted, then our + * impression of the hostpt and acme's + * may be different. This could be seen if you + * hit enter a bunch of times in a con + * session. To work around the unreliability, + * only send input if we don't have an S pending. + * The same race occurs between when a character + * is typed and when we get notice of it, but + * since characters tend to be typed at the end + * of the buffer, we don't run into it. There's + * no workaround possible for this typing race, + * since we can't tell when the user has typed + * something but we just haven't been notified. + */ + if(pendingS) + pendingK = 1; + else + hostpt += sendinput(w, hostpt, &endpt); + } + break; + } + break; + + case 'M': /* mouse */ + delta = e->q1-e->q0; + switch(e->c2){ + case 'x': + case 'X': + execevent(w, e, command); + break; + + case 'l': /* reflect all searches back to acme */ + case 'L': + if(e->flag & 2) + recvp(w->cevent); + winwriteevent(w, e); + break; + + case 'I': + endpt += delta; + if(e->q0 < hostpt) + hostpt += delta; + else + hostpt += sendinput(w, hostpt, &endpt); + break; + + case 'D': + endpt -= delta; + if(e->q1 < hostpt) + hostpt -= delta; + else if(e->q0 < hostpt) + hostpt = e->q0; + break; + case 'd': /* modify away; we don't care */ + case 'i': + break; + + default: + goto Unknown; + } + } + } +} + +enum +{ + NARGS = 100, + NARGCHAR = 8*1024, + EXECSTACK = STACK+(NARGS+1)*sizeof(char*)+NARGCHAR +}; + +struct Exec +{ + char **argv; + Channel *cpid; +}; + +int +lookinbin(char *s) +{ + if(s[0] == '/') + return 0; + if(s[0]=='.' && s[1]=='/') + return 0; + if(s[0]=='.' && s[1]=='.' && s[2]=='/') + return 0; + return 1; +} + +/* adapted from mail. not entirely free of details from that environment */ +void +execproc(void *v) +{ + struct Exec *e; + char *cmd, **av; + Channel *cpid; + + e = v; + rfork(RFCFDG|RFNOTEG); + av = e->argv; + close(0); + open("/dev/cons", OREAD); + close(1); + open("/dev/cons", OWRITE); + dup(1, 2); + cpid = e->cpid; + free(e); + procexec(cpid, av[0], av); + if(lookinbin(av[0])){ + cmd = estrstrdup("/bin/", av[0]); + procexec(cpid, cmd, av); + } + error("can't exec %s: %r", av[0]); +} + +void +startcmd(char *argv[], int *notepg) +{ + struct Exec *e; + Channel *cpid; + char buf[64]; + int pid; + + e = emalloc(sizeof(struct Exec)); + e->argv = argv; + cpid = chancreate(sizeof(ulong), 0); + e->cpid = cpid; + sprint(buf, "/mnt/wsys/%d", win->id); + bind(buf, "/dev/acme", MREPL); + proccreate(execproc, e, EXECSTACK); + do + pid = recvul(cpid); + while(pid == -1); + sprint(buf, "/proc/%d/notepg", pid); + *notepg = open(buf, OWRITE); +} diff --git a/acme/bin/source/win/mkfile b/acme/bin/source/win/mkfile new file mode 100644 index 000000000..608f95b86 --- /dev/null +++ b/acme/bin/source/win/mkfile @@ -0,0 +1,25 @@ +syms + 8c -aa util.c win.c >>syms diff --git a/acme/bin/source/win/pipe.c b/acme/bin/source/win/pipe.c new file mode 100644 index 000000000..280f726c9 --- /dev/null +++ b/acme/bin/source/win/pipe.c @@ -0,0 +1,175 @@ +#include +#include +#include +#include +#include <9p.h> +#include "dat.h" + +typedef struct Wpid Wpid; +struct Wpid +{ + int pid; + Window *w; + Wpid *next; +}; + +void pipectl(void*); + +int pipefd; +Wpid *wpid; +int snarffd; +Channel *newpipechan; + +int +newpipewin(int pid, char *p) +{ + int id; + Window *w; + Wpid *wp; + + w = newwindow(); + winname(w, p); + wintagwrite(w, "Send ", 5); + wp = emalloc(sizeof(Wpid)); + wp->pid = pid; + wp->w = w; + wp->next = wpid; /* BUG: this happens in fsread proc (we don't use wpid, so it's okay) */ + wpid = wp; + id = w->id; + sendp(newpipechan, w); + return id; +} + +int +pipecommand(Window *w, char *s) +{ + ulong q0, q1; + char tmp[32], *t; + int n, k; + + while(*s==' ' || *s=='\t' || *s=='\n') + s++; + if(strcmp(s, "Delete")==0){ + windel(w, 1); + threadexits(nil); + return 1; + } + if(strcmp(s, "Del")==0){ + if(windel(w, 0)) + threadexits(nil); + return 1; + } + if(strcmp(s, "Send") == 0){ + if(w->addr < 0) + w->addr = winopenfile(w, "addr"); + ctlprint(w->ctl, "addr=dot\n"); + seek(w->addr, 0UL, 0); + if(read(w->addr, tmp, 2*12) == 2*12){ + q0 = atol(tmp+0*12); + q1 = atol(tmp+1*12); + if(q0 == q1){ + t = nil; + k = 0; + if(snarffd > 0){ + seek(0, snarffd, 0); + for(;;){ + t = realloc(t, k+8192+2); + if(t == nil) + error("alloc failed: %r\n"); + n = read(snarffd, t+k, 8192); + if(n <= 0) + break; + k += n; + } + t[k] = 0; + } + }else{ + t = emalloc((q1-q0)*UTFmax+2); + winread(w, q0, q1, t); + k = strlen(t); + } + if(t!=nil && t[0]!='\0'){ + if(t[k-1]!='\n' && t[k-1]!='\004'){ + t[k++] = '\n'; + t[k] = '\0'; + } + sendit(t); + } + free(t); + } + return 1; + } + return 0; +} + +void +pipectl(void *v) +{ + Window *w; + Event *e; + + w = v; + proccreate(wineventproc, w, STACK); + + windormant(w); + winsetaddr(w, "0", 0); + for(;;){ + e = recvp(w->cevent); + switch(e->c1){ + default: + Unknown: + fprint(2, "unknown message %c%c\n", e->c1, e->c2); + break; + + case 'E': /* write to body; can't affect us */ + break; + + case 'F': /* generated by our actions; ignore */ + break; + + case 'K': /* ignore */ + break; + + case 'M': + switch(e->c2){ + case 'x': + case 'X': + execevent(w, e, pipecommand); + break; + + case 'l': /* reflect all searches back to acme */ + case 'L': + if(e->flag & 2) + recvp(w->cevent); + winwriteevent(w, e); + break; + + case 'I': /* modify away; we don't care */ + case 'i': + case 'D': + case 'd': + break; + + default: + goto Unknown; + } + } + } +} + +void +newpipethread(void*) +{ + Window *w; + + while(w = recvp(newpipechan)) + threadcreate(pipectl, w, STACK); +} + +void +startpipe(void) +{ + newpipechan = chancreate(sizeof(Window*), 0); + threadcreate(newpipethread, nil, STACK); + snarffd = open("/dev/snarf", OREAD|OCEXEC); +} diff --git a/acme/bin/source/win/util.c b/acme/bin/source/win/util.c new file mode 100644 index 000000000..ec8bd0c99 --- /dev/null +++ b/acme/bin/source/win/util.c @@ -0,0 +1,90 @@ +#include +#include +#include +#include +#include "dat.h" + +void* +emalloc(uint n) +{ + void *p; + + p = malloc(n); + if(p == nil) + error("can't malloc: %r"); + memset(p, 0, n); + return p; +} + +char* +estrdup(char *s) +{ + char *t; + + t = emalloc(strlen(s)+1); + strcpy(t, s); + return t; +} + +char* +estrstrdup(char *s, char *t) +{ + char *u; + + u = emalloc(strlen(s)+strlen(t)+1); + sprint(u, "%s%s", s, t); + return u; +} + +char* +eappend(char *s, char *sep, char *t) +{ + char *u; + + if(t == nil) + u = estrstrdup(s, sep); + else{ + u = emalloc(strlen(s)+strlen(sep)+strlen(t)+1); + sprint(u, "%s%s%s", s, sep, t); + } + free(s); + return u; +} + +char* +egrow(char *s, char *sep, char *t) +{ + s = eappend(s, sep, t); + free(t); + return s; +} + +void +error(char *fmt, ...) +{ + Fmt f; + char buf[64]; + va_list arg; + + fmtfdinit(&f, 2, buf, sizeof buf); + fmtprint(&f, "win: "); + va_start(arg, fmt); + fmtvprint(&f, fmt, arg); + va_end(arg); + fmtprint(&f, "\n"); + fmtfdflush(&f); + threadexitsall(fmt); +} + +void +ctlprint(int fd, char *fmt, ...) +{ + int n; + va_list arg; + + va_start(arg, fmt); + n = vfprint(fd, fmt, arg); + va_end(arg); + if(n <= 0) + error("control file write error: %r"); +} diff --git a/acme/bin/source/win/win.c b/acme/bin/source/win/win.c new file mode 100644 index 000000000..2e7f883ed --- /dev/null +++ b/acme/bin/source/win/win.c @@ -0,0 +1,264 @@ +#include +#include +#include +#include +#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; inr; 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; iaddr < 0) + w->addr = winopenfile(w, "addr"); + if(w->data < 0) + w->data = winopenfile(w, "data"); + m = q0; + nb = 0; + while(m < q1){ + n = sprint(buf, "#%d", m); + if(write(w->addr, buf, n) != n) + error("error writing addr: %r"); + n = read(w->data, buf, sizeof buf); + if(n < 0) + error("reading data: %r"); + nr = nrunes(buf, n); + while(m+nr >q1){ + do; while(n>0 && (buf[--n]&0xC0)==0x80); + --nr; + } + if(n == 0) + break; + memmove(data, buf, n); + nb += n; + data += n; + *data = 0; + m += nr; + } + return nb; +} + +void +windormant(Window *w) +{ + if(w->addr >= 0){ + close(w->addr); + w->addr = -1; + } + if(w->body >= 0){ + close(w->body); + w->body = -1; + } + if(w->data >= 0){ + close(w->data); + w->data = -1; + } +} + +int +windel(Window *w, int sure) +{ + if(sure) + write(w->ctl, "delete\n", 7); + else if(write(w->ctl, "del\n", 4) != 4) + return 0; + /* event proc will die due to read error from event file */ + windormant(w); + close(w->ctl); + w->ctl = -1; + close(w->event); + w->event = -1; + return 1; +} + +void +winclean(Window *w) +{ + ctlprint(w->ctl, "clean\n"); +} + +int +winsetaddr(Window *w, char *addr, int errok) +{ + if(w->addr < 0) + w->addr = winopenfile(w, "addr"); + if(write(w->addr, addr, strlen(addr)) < 0){ + if(!errok) + error("error writing addr(%s): %r", addr); + return 0; + } + return 1; +} + +int +winselect(Window *w, char *addr, int errok) +{ + if(winsetaddr(w, addr, errok)){ + ctlprint(w->ctl, "dot=addr\n"); + return 1; + } + return 0; +} diff --git a/68020/bin/ip/.dummy b/acme/bin/sparc/.dummy similarity index 100% rename from 68020/bin/ip/.dummy rename to acme/bin/sparc/.dummy diff --git a/68020/lib/.dummy b/acme/bin/sparc64/.dummy similarity index 100% rename from 68020/lib/.dummy rename to acme/bin/sparc64/.dummy diff --git a/acme/bin/unind b/acme/bin/unind new file mode 100755 index 000000000..6b1e59f35 --- /dev/null +++ b/acme/bin/unind @@ -0,0 +1,3 @@ +#!/bin/rc + +sed 's/^ //' $* diff --git a/acme/bin/wnew b/acme/bin/wnew new file mode 100755 index 000000000..f1362175d --- /dev/null +++ b/acme/bin/wnew @@ -0,0 +1,5 @@ +#!/bin/rc -e + +id=`{mkwnew $*} +cat >/mnt/acme/$id/body +echo clean >/mnt/acme/$id/ctl diff --git a/acme/mail/guide b/acme/mail/guide new file mode 100644 index 000000000..8977ac7c8 --- /dev/null +++ b/acme/mail/guide @@ -0,0 +1,4 @@ +Mail stored +plumb /mail/box/$user/names +mail -'x' someaddress +mkbox /mail/box/$user/new_box diff --git a/acme/mail/mkbox b/acme/mail/mkbox new file mode 100755 index 000000000..3e98afc7b --- /dev/null +++ b/acme/mail/mkbox @@ -0,0 +1,11 @@ +#!/bin/rc + +for(i){ + if(! test -e $i){ + if(cp /dev/null $i){ + chmod 600 $i + chmod +al $i + } + } + if not echo $i already exists +} diff --git a/acme/mail/readme b/acme/mail/readme new file mode 100644 index 000000000..b795a04e7 --- /dev/null +++ b/acme/mail/readme @@ -0,0 +1,57 @@ +The Acme Mail program uses upas/fs to parse the mail box, and then +presents a file-browser-like user interface to reading and sending +messages. The Mail window presents each numbered message like the +contents of a directory presented one per line. If a message has a +Subject: line, that is shown indented on the following line. +Multipart MIME-encoded messages are presented in the obvious +hierarchical format. + +Mail uses upas/fs to access the mail box. By default it reads "mbox", +the standard user mail box. If Mail is given an argument, it is +passed to upas/fs as the name of the mail box (or upas/fs directory) +to open. + +Although Mail works if the plumber is not running, it's designed to be +run with plumbing enabled and many of its features work best if it is. + +The mailbox window has a few commands: Put writes back the mailbox; +Mail creates a new window in which to compose a message; and Delmesg +deletes messages by number. The number may be given as argument or +indicated by selecting the header line in the mailbox window. +(Delmesg does not expand null selections, in the interest of safety.) + +Clicking the right button on a message number opens it; clicking on +any of the subparts of a message opens that (and also opens the +message itself). Each message window has a few commands in the tag +with obvious names: Reply, Delmsg, etc. "Reply" replies to the single +sender of the message, "Reply all" or "Replyall" replies to everyone +in the From:, To:, and CC: lines. + +Message parts with recognized MIME types such as image/jpeg are sent +to the plumber for further dispatch. Acme Mail also listens to +messages on the seemail and showmail plumbing ports, to report the +arrival of new messages (highlighting the entry; right-click on the +entry to open the message) and open them if you right-click on the +face in the faces window. + +When composing a mail message or replying to a message, the first line +of the text is a list of recipients of the message. To:, and CC:, and BCC: +lines are interpreted in the usual way. Two other header lines are +special to Acme Mail: + Include: file places a copy of file in the message as an + inline MIME attachment. + Attach: file places a copy of file in the message as a regular + MIME attachment. + +Acme Mail uses these conventions when replying to messages, +constructing headers for the default behavior. You may edit these to +change behavior. Most important, when replying to a message Mail will +always Include: the original message; delete that line if you don't +want to include it. + +If the mailbox + /mail/box/$user/outgoing +exists, Acme Mail will save your a copy of your outgoing messages +there. Attachments are described in the copy but not included. + +The -m mntpoint flag specifies a different mount point for /upas/fs. diff --git a/acme/mail/src/dat.h b/acme/mail/src/dat.h new file mode 100644 index 000000000..0b0b60a70 --- /dev/null +++ b/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; diff --git a/acme/mail/src/html.c b/acme/mail/src/html.c new file mode 100644 index 000000000..64730856f --- /dev/null +++ b/acme/mail/src/html.c @@ -0,0 +1,75 @@ +#include +#include +#include +#include +#include +#include +#include "dat.h" + + +char* +formathtml(char *body, int *np) +{ + int i, j, p[2], q[2]; + Exec *e; + char buf[1024]; + Channel *sync; + + e = emalloc(sizeof(struct Exec)); + if(pipe(p) < 0 || pipe(q) < 0) + error("can't create pipe: %r"); + + e->p[0] = p[0]; + e->p[1] = p[1]; + e->q[0] = q[0]; + e->q[1] = q[1]; + e->argv = emalloc(3*sizeof(char*)); + e->argv[0] = estrdup("htmlfmt"); + e->argv[1] = estrdup("-cutf-8"); + e->argv[2] = nil; + e->prog = "/bin/htmlfmt"; + sync = chancreate(sizeof(int), 0); + e->sync = sync; + proccreate(execproc, e, EXECSTACK); + recvul(sync); + close(p[0]); + close(q[1]); + + if((i=write(p[1], body, *np)) != *np){ + fprint(2, "Mail: warning: htmlfmt failed: wrote %d of %d: %r\n", i, *np); + close(p[1]); + close(q[0]); + return body; + } + close(p[1]); + + free(body); + body = nil; + i = 0; + for(;;){ + j = read(q[0], buf, sizeof buf); + if(j <= 0) + break; + body = realloc(body, i+j+1); + if(body == nil) + error("realloc failed: %r"); + memmove(body+i, buf, j); + i += j; + body[i] = '\0'; + } + close(q[0]); + + *np = i; + return body; +} + +char* +readbody(char *type, char *dir, int *np) +{ + char *body; + + body = readfile(dir, "body", np); + if(body != nil && strcmp(type, "text/html") == 0) + return formathtml(body, np); + return body; +} diff --git a/acme/mail/src/mail.c b/acme/mail/src/mail.c new file mode 100644 index 000000000..1e1ff0b8b --- /dev/null +++ b/acme/mail/src/mail.c @@ -0,0 +1,550 @@ +#include +#include +#include +#include +#include +#include +#include "dat.h" + +char *maildir = "/mail/fs/"; /* mountpoint of mail file system */ +char *mailtermdir = "/mnt/term/mail/fs/"; /* alternate mountpoint */ +char *mboxname = "mbox"; /* mailboxdir/mboxname is mail spool file */ +char *mailboxdir = nil; /* nil == /mail/box/$user */ +char *fsname; /* filesystem for mailboxdir/mboxname is at maildir/fsname */ +char *user; +char *outgoing; + +Window *wbox; +Message mbox; +Message replies; +char *home; +int plumbsendfd; +int plumbseemailfd; +int plumbshowmailfd; +int plumbsendmailfd; +Channel *cplumb; +Channel *cplumbshow; +Channel *cplumbsend; +int wctlfd; +void mainctl(void*); +void plumbproc(void*); +void plumbshowproc(void*); +void plumbsendproc(void*); +void plumbthread(void); +void plumbshowthread(void*); +void plumbsendthread(void*); + +int shortmenu; + +void +usage(void) +{ + fprint(2, "usage: Mail [-sS] [-o outgoing] [mailboxname [directoryname]]\n"); + threadexitsall("usage"); +} + +void +removeupasfs(void) +{ + char buf[256]; + + if(strcmp(mboxname, "mbox") == 0) + return; + snprint(buf, sizeof buf, "close %s", mboxname); + write(mbox.ctlfd, buf, strlen(buf)); +} + +int +ismaildir(char *s) +{ + char buf[256]; + Dir *d; + int ret; + + snprint(buf, sizeof buf, "%s%s", maildir, s); + d = dirstat(buf); + if(d == nil) + return 0; + ret = d->qid.type & QTDIR; + free(d); + return ret; +} + +void +threadmain(int argc, char *argv[]) +{ + char *s, *name; + char err[ERRMAX], *cmd; + int i, newdir; + Fmt fmt; + + doquote = needsrcquote; + quotefmtinstall(); + + /* open these early so we won't miss notification of new mail messages while we read mbox */ + plumbsendfd = plumbopen("send", OWRITE|OCEXEC); + plumbseemailfd = plumbopen("seemail", OREAD|OCEXEC); + plumbshowmailfd = plumbopen("showmail", OREAD|OCEXEC); + + shortmenu = 0; + ARGBEGIN{ + case 's': + shortmenu = 1; + break; + case 'S': + shortmenu = 2; + break; + case 'o': + outgoing = EARGF(usage()); + break; + case 'm': + smprint(maildir, "%s/", EARGF(usage())); + break; + default: + usage(); + }ARGEND + + name = "mbox"; + + /* bind the terminal /mail/fs directory over the local one */ + if(access(maildir, 0)<0 && access(mailtermdir, 0)==0) + bind(mailtermdir, maildir, MAFTER); + + newdir = 1; + if(argc > 0){ + i = strlen(argv[0]); + if(argc>2 || i==0) + usage(); + /* see if the name is that of an existing /mail/fs directory */ + if(argc==1 && strchr(argv[0], '/')==0 && ismaildir(argv[0])){ + name = argv[0]; + mboxname = eappend(estrdup(maildir), "", name); + newdir = 0; + }else{ + if(argv[0][i-1] == '/') + argv[0][i-1] = '\0'; + s = strrchr(argv[0], '/'); + if(s == nil) + mboxname = estrdup(argv[0]); + else{ + *s++ = '\0'; + if(*s == '\0') + usage(); + mailboxdir = argv[0]; + mboxname = estrdup(s); + } + if(argc > 1) + name = argv[1]; + else + name = mboxname; + } + } + + user = getenv("user"); + if(user == nil) + user = "none"; + if(mailboxdir == nil) + mailboxdir = estrstrdup("/mail/box/", user); + if(outgoing == nil) + outgoing = estrstrdup(mailboxdir, "/outgoing"); + + s = estrstrdup(maildir, "ctl"); + mbox.ctlfd = open(s, ORDWR|OCEXEC); + if(mbox.ctlfd < 0) + error("can't open %s: %r", s); + + fsname = estrdup(name); + if(newdir && argc > 0){ + s = emalloc(5+strlen(mailboxdir)+strlen(mboxname)+strlen(name)+10+1); + for(i=0; i<10; i++){ + sprint(s, "open %s/%s %s", mailboxdir, mboxname, fsname); + if(write(mbox.ctlfd, s, strlen(s)) >= 0) + break; + err[0] = '\0'; + errstr(err, sizeof err); + if(strstr(err, "mbox name in use") == nil) + error("can't create directory %s for mail: %s", name, err); + free(fsname); + fsname = emalloc(strlen(name)+10); + sprint(fsname, "%s-%d", name, i); + } + if(i == 10) + error("can't open %s/%s: %r", mailboxdir, mboxname); + free(s); + } + + s = estrstrdup(fsname, "/"); + mbox.name = estrstrdup(maildir, s); + mbox.level= 0; + readmbox(&mbox, maildir, s); + home = getenv("home"); + if(home == nil) + home = "/"; + + wbox = newwindow(); + winname(wbox, mbox.name); + wintagwrite(wbox, "Put Mail Delmesg ", 3+1+4+1+7+1); + threadcreate(mainctl, wbox, STACK); + + fmtstrinit(&fmt); + fmtprint(&fmt, "Mail"); + if(shortmenu) + fmtprint(&fmt, " -%c", "sS"[shortmenu-1]); + if(outgoing) + fmtprint(&fmt, " -o %s", outgoing); + fmtprint(&fmt, " %s", name); + cmd = fmtstrflush(&fmt); + if(cmd == nil) + sysfatal("out of memory"); + winsetdump(wbox, "/acme/mail", cmd); + mbox.w = wbox; + + mesgmenu(wbox, &mbox); + winclean(wbox); + + wctlfd = open("/dev/wctl", OWRITE|OCEXEC); /* for acme window */ + cplumb = chancreate(sizeof(Plumbmsg*), 0); + cplumbshow = chancreate(sizeof(Plumbmsg*), 0); + if(strcmp(name, "mbox") == 0){ + /* + * Avoid creating multiple windows to send mail by only accepting + * sendmail plumb messages if we're reading the main mailbox. + */ + plumbsendmailfd = plumbopen("sendmail", OREAD|OCEXEC); + cplumbsend = chancreate(sizeof(Plumbmsg*), 0); + proccreate(plumbsendproc, nil, STACK); + threadcreate(plumbsendthread, nil, STACK); + } + /* start plumb reader as separate proc ... */ + proccreate(plumbproc, nil, STACK); + proccreate(plumbshowproc, nil, STACK); + threadcreate(plumbshowthread, nil, STACK); + /* ... and use this thread to read the messages */ + plumbthread(); +} + +void +plumbproc(void*) +{ + Plumbmsg *m; + + threadsetname("plumbproc"); + for(;;){ + m = plumbrecv(plumbseemailfd); + sendp(cplumb, m); + if(m == nil) + threadexits(nil); + } +} + +void +plumbshowproc(void*) +{ + Plumbmsg *m; + + threadsetname("plumbshowproc"); + for(;;){ + m = plumbrecv(plumbshowmailfd); + sendp(cplumbshow, m); + if(m == nil) + threadexits(nil); + } +} + +void +plumbsendproc(void*) +{ + Plumbmsg *m; + + threadsetname("plumbsendproc"); + for(;;){ + m = plumbrecv(plumbsendmailfd); + sendp(cplumbsend, m); + if(m == nil) + threadexits(nil); + } +} + +void +newmesg(char *name, char *digest) +{ + Dir *d; + + if(strncmp(name, mbox.name, strlen(mbox.name)) != 0) + return; /* message is about another mailbox */ + if(mesglookupfile(&mbox, name, digest) != nil) + return; + d = dirstat(name); + if(d == nil) + return; + if(mesgadd(&mbox, mbox.name, d, digest)) + mesgmenunew(wbox, &mbox); + free(d); +} + +void +showmesg(char *name, char *digest) +{ + char *n; + + if(strncmp(name, mbox.name, strlen(mbox.name)) != 0) + return; /* message is about another mailbox */ + n = estrdup(name+strlen(mbox.name)); + if(n[strlen(n)-1] != '/') + n = egrow(n, "/", nil); + mesgopen(&mbox, mbox.name, name+strlen(mbox.name), nil, 1, digest); + free(n); +} + +void +delmesg(char *name, char *digest, int dodel) +{ + Message *m; + + m = mesglookupfile(&mbox, name, digest); + if(m != nil){ + mesgmenumarkdel(wbox, &mbox, m, 0); + if(dodel) + m->writebackdel = 1; + } +} + +void +plumbthread(void) +{ + Plumbmsg *m; + Plumbattr *a; + char *type, *digest; + + threadsetname("plumbthread"); + while((m = recvp(cplumb)) != nil){ + a = m->attr; + digest = plumblookup(a, "digest"); + type = plumblookup(a, "mailtype"); + if(type == nil) + fprint(2, "Mail: plumb message with no mailtype attribute\n"); + else if(strcmp(type, "new") == 0) + newmesg(m->data, digest); + else if(strcmp(type, "delete") == 0) + delmesg(m->data, digest, 0); + else + fprint(2, "Mail: unknown plumb attribute %s\n", type); + plumbfree(m); + } + threadexits(nil); +} + +void +plumbshowthread(void*) +{ + Plumbmsg *m; + + threadsetname("plumbshowthread"); + while((m = recvp(cplumbshow)) != nil){ + showmesg(m->data, plumblookup(m->attr, "digest")); + plumbfree(m); + } + threadexits(nil); +} + +void +plumbsendthread(void*) +{ + Plumbmsg *m; + + threadsetname("plumbsendthread"); + while((m = recvp(cplumbsend)) != nil){ + mkreply(nil, "Mail", m->data, m->attr, nil); + plumbfree(m); + } + threadexits(nil); +} + +int +mboxcommand(Window *w, char *s) +{ + char *args[10], **targs; + Message *m, *next; + int ok, nargs, i, j; + char buf[128]; + + nargs = tokenize(s, args, nelem(args)); + if(nargs == 0) + return 0; + if(strcmp(args[0], "Mail") == 0){ + if(nargs == 1) + mkreply(nil, "Mail", "", nil, nil); + else + mkreply(nil, "Mail", args[1], nil, nil); + return 1; + } + if(strcmp(s, "Del") == 0){ + if(mbox.dirty){ + mbox.dirty = 0; + fprint(2, "mail: mailbox not written\n"); + return 1; + } + ok = 1; + for(m=mbox.head; m!=nil; m=next){ + next = m->next; + if(m->w){ + if(windel(m->w, 0)) + m->w = nil; + else + ok = 0; + } + } + for(m=replies.head; m!=nil; m=next){ + next = m->next; + if(m->w){ + if(windel(m->w, 0)) + m->w = nil; + else + ok = 0; + } + } + if(ok){ + windel(w, 1); + removeupasfs(); + threadexitsall(nil); + } + return 1; + } + if(strcmp(s, "Put") == 0){ + rewritembox(wbox, &mbox); + return 1; + } + if(strcmp(s, "Delmesg") == 0){ + if(nargs > 1){ + for(i=1; icevent); + switch(e->c1){ + default: + Unknown: + print("unknown message %c%c\n", e->c1, e->c2); + break; + + case 'E': /* write to body; can't affect us */ + break; + + case 'F': /* generated by our actions; ignore */ + break; + + case 'K': /* type away; we don't care */ + break; + + case 'M': + switch(e->c2){ + case 'x': + case 'X': + ea = nil; + e2 = nil; + if(e->flag & 2) + e2 = recvp(w->cevent); + if(e->flag & 8){ + ea = recvp(w->cevent); + na = ea->nb; + recvp(w->cevent); + }else + na = 0; + s = e->b; + /* if it's a known command, do it */ + if((e->flag&2) && e->nb==0) + s = e2->b; + if(na){ + t = emalloc(strlen(s)+1+na+1); + sprint(t, "%s %s", s, ea->b); + s = t; + } + /* if it's a long message, it can't be for us anyway */ + if(!mboxcommand(w, s)) /* send it back */ + winwriteevent(w, e); + if(na) + free(s); + break; + + case 'l': + case 'L': + buf = nil; + eq = e; + if(e->flag & 2){ + e2 = recvp(w->cevent); + eq = e2; + } + s = eq->b; + if(eq->q1>eq->q0 && eq->nb==0){ + buf = emalloc((eq->q1-eq->q0)*UTFmax+1); + winread(w, eq->q0, eq->q1, buf); + s = buf; + } + nopen = 0; + do{ + /* skip 'deleted' string if present' */ + if(strncmp(s, deleted, strlen(deleted)) == 0) + s += strlen(deleted); + /* skip mail box name if present */ + if(strncmp(s, mbox.name, strlen(mbox.name)) == 0) + s += strlen(mbox.name); + nopen += mesgopen(&mbox, mbox.name, s, nil, 0, nil); + while(*s!='\0' && *s++!='\n') + ; + }while(*s); + if(nopen == 0) /* send it back */ + winwriteevent(w, e); + free(buf); + break; + + case 'I': /* modify away; we don't care */ + case 'D': + case 'd': + case 'i': + break; + + default: + goto Unknown; + } + } + } +} + diff --git a/acme/mail/src/mesg.c b/acme/mail/src/mesg.c new file mode 100644 index 000000000..620e76e3e --- /dev/null +++ b/acme/mail/src/mesg.c @@ -0,0 +1,1322 @@ +#include +#include +#include +#include +#include +#include +#include "dat.h" + +enum +{ + DIRCHUNK = 32*sizeof(Dir) +}; + +char regexchars[] = "\\/[].+?()*^$"; +char deleted[] = "(deleted)-"; +char deletedrx[] = "\\(deleted\\)-"; +char deletedrx01[] = "(\\(deleted\\)-)?"; +char deletedaddr[] = "-#0;/^\\(deleted\\)-/"; + +struct{ + char *type; + char *port; + char *suffix; +} ports[] = { + "text/", "edit", ".txt", + /* text must be first for plumbport() */ + "image/gif", "image", ".gif", + "image/jpeg", "image", ".jpg", + "image/jpeg", "image", ".jpeg", + "image/png", "image", ".png", + "image/tiff", "image", ".tif", + "application/postscript", "postscript", ".ps", + "application/pdf", "postscript", ".pdf", + "application/msword", "msword", ".doc", + "application/rtf", "msword", ".rtf", + "audio/x-wav", "wav", ".wav", + nil, nil +}; + +char *goodtypes[] = { + "text", + "text/plain", + "message/rfc822", + "text/richtext", + "text/tab-separated-values", + "application/octet-stream", + nil, +}; + +struct{ + char *type; + char *ext; +} exts[] = { + "image/gif", ".gif", + "image/jpeg", ".jpg", + nil, nil +}; + +char *okheaders[] = +{ + "From:", + "Date:", + "To:", + "CC:", + "Subject:", + nil +}; + +char *extraheaders[] = +{ + "Resent-From:", + "Resent-To:", + "Sort:", + nil, +}; + +char* +line(char *data, char **pp) +{ + char *p, *q; + + for(p=data; *p!='\0' && *p!='\n'; p++) + ; + if(*p == '\n') + *pp = p+1; + else + *pp = p; + q = emalloc(p-data + 1); + memmove(q, data, p-data); + return q; +} + +void +scanheaders(Message *m, char *dir) +{ + char *s, *t, *u, *f; + + s = f = readfile(dir, "header", nil); + if(s != nil) + while(*s){ + t = line(s, &s); + if(strncmp(t, "From: ", 6) == 0){ + m->fromcolon = estrdup(t+6); + /* remove all quotes; they're ugly and irregular */ + for(u=m->fromcolon; *u; u++) + if(*u == '"') + memmove(u, u+1, strlen(u)); + } + if(strncmp(t, "Subject: ", 9) == 0) + m->subject = estrdup(t+9); + free(t); + } + if(m->fromcolon == nil) + m->fromcolon = estrdup(m->from); + free(f); +} + +int +loadinfo(Message *m, char *dir) +{ + int n; + char *data, *p, *s; + + data = readfile(dir, "info", &n); + if(data == nil) + return 0; + m->from = line(data, &p); + scanheaders(m, dir); /* depends on m->from being set */ + m->to = line(p, &p); + m->cc = line(p, &p); + m->replyto = line(p, &p); + m->date = line(p, &p); + s = line(p, &p); + if(m->subject == nil) + m->subject = s; + else + free(s); + m->type = line(p, &p); + m->disposition = line(p, &p); + m->filename = line(p, &p); + m->digest = line(p, &p); + free(data); + return 1; +} + +int +isnumeric(char *s) +{ + while(*s){ + if(!isdigit(*s)) + return 0; + s++; + } + return 1; +} + +Dir* +loaddir(char *name, int *np) +{ + int fd; + Dir *dp; + + fd = open(name, OREAD); + if(fd < 0) + return nil; + *np = dirreadall(fd, &dp); + close(fd); + return dp; +} + +void +readmbox(Message *mbox, char *dir, char *subdir) +{ + char *name; + Dir *d, *dirp; + int i, n; + + name = estrstrdup(dir, subdir); + dirp = loaddir(name, &n); + mbox->recursed = 1; + if(dirp) + for(i=0; iname)) + 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; jsubject); + } + return i; +} + +void +mesgmenu0(Window *w, Message *mbox, char *realdir, char *dir, int ind, Biobuf *fd, int onlyone, int dotail) +{ + int i; + Message *m; + char *name, *tmp; + int ogf=0; + + if(strstr(realdir, "outgoing") != nil) + ogf=1; + + /* show mail box in reverse order, pieces in forward order */ + if(ind > 0) + m = mbox->head; + else + m = mbox->tail; + while(m != nil){ + for(i=0; iname); + tmp = info(m, ind, ogf); + Bprint(fd, "%s%s\n", name, tmp); + free(tmp); + if(dotail && m->tail) + mesgmenu0(w, m, realdir, name, ind+1, fd, 0, dotail); + free(name); + if(ind) + m = m->next; + else + m = m->prev; + if(onlyone) + m = nil; + } +} + +void +mesgmenu(Window *w, Message *mbox) +{ + winopenbody(w, OWRITE); + mesgmenu0(w, mbox, mbox->name, "", 0, w->body, 0, !shortmenu); + winclosebody(w); +} + +/* one new message has arrived, as mbox->tail */ +void +mesgmenunew(Window *w, Message *mbox) +{ + Biobuf *b; + + winselect(w, "0", 0); + w->data = winopenfile(w, "data"); + b = emalloc(sizeof(Biobuf)); + Binit(b, w->data, OWRITE); + mesgmenu0(w, mbox, mbox->name, "", 0, b, 1, !shortmenu); + Bterm(b); + free(b); + if(!mbox->dirty) + winclean(w); + /* select tag line plus following indented lines, but not final newline (it's distinctive) */ + winselect(w, "0/.*\\n((\t.*\\n)*\t.*)?/", 1); + close(w->addr); + close(w->data); + w->addr = -1; + w->data = -1; +} + +char* +name2regexp(char *prefix, char *s) +{ + char *buf, *p, *q; + + buf = emalloc(strlen(prefix)+2*strlen(s)+50); /* leave room to append more */ + p = buf; + *p++ = '0'; + *p++ = '/'; + *p++ = '^'; + strcpy(p, prefix); + p += strlen(prefix); + for(q=s; *q!='\0'; q++){ + if(strchr(regexchars, *q) != nil) + *p++ = '\\'; + *p++ = *q; + } + *p++ = '/'; + *p = '\0'; + return buf; +} + +void +mesgmenumarkdel(Window *w, Message *mbox, Message *m, int writeback) +{ + char *buf; + + + if(m->deleted) + return; + m->writebackdel = writeback; + if(w->data < 0) + w->data = winopenfile(w, "data"); + buf = name2regexp("", m->name); + strcat(buf, "-#0"); + if(winselect(w, buf, 1)) + write(w->data, deleted, 10); + free(buf); + close(w->data); + close(w->addr); + w->addr = w->data = -1; + mbox->dirty = 1; + m->deleted = 1; +} + +void +mesgmenumarkundel(Window *w, Message*, Message *m) +{ + char *buf; + + if(m->deleted == 0) + return; + if(w->data < 0) + w->data = winopenfile(w, "data"); + buf = name2regexp(deletedrx, m->name); + if(winselect(w, buf, 1)) + if(winsetaddr(w, deletedaddr, 1)) + write(w->data, "", 0); + free(buf); + close(w->data); + close(w->addr); + w->addr = w->data = -1; + m->deleted = 0; +} + +void +mesgmenudel(Window *w, Message *mbox, Message *m) +{ + char *buf; + + if(w->data < 0) + w->data = winopenfile(w, "data"); + buf = name2regexp(deletedrx, m->name); + if(winsetaddr(w, buf, 1) && winsetaddr(w, ".,./.*\\n(\t.*\\n)*/", 1)) + write(w->data, "", 0); + free(buf); + close(w->data); + close(w->addr); + w->addr = w->data = -1; + mbox->dirty = 1; + m->deleted = 1; +} + +void +mesgmenumark(Window *w, char *which, char *mark) +{ + char *buf; + + if(w->data < 0) + w->data = winopenfile(w, "data"); + buf = name2regexp(deletedrx01, which); + if(winsetaddr(w, buf, 1) && winsetaddr(w, "+0-#1", 1)) /* go to end of line */ + write(w->data, mark, strlen(mark)); + free(buf); + close(w->data); + close(w->addr); + w->addr = w->data = -1; + if(!mbox.dirty) + winclean(w); +} + +void +mesgfreeparts(Message *m) +{ + free(m->name); + free(m->replyname); + free(m->fromcolon); + free(m->from); + free(m->to); + free(m->cc); + free(m->replyto); + free(m->date); + free(m->subject); + free(m->type); + free(m->disposition); + free(m->filename); + free(m->digest); +} + +void +mesgdel(Message *mbox, Message *m) +{ + Message *n, *next; + + if(m->opened) + error("internal error: deleted message still open in mesgdel"); + /* delete subparts */ + for(n=m->head; n!=nil; n=next){ + next = n->next; + mesgdel(m, n); + } + /* remove this message from list */ + if(m->next) + m->next->prev = m->prev; + else + mbox->tail = m->prev; + if(m->prev) + m->prev->next = m->next; + else + mbox->head = m->next; + + mesgfreeparts(m); +} + +int +mesgsave(Message *m, char *s) +{ + int ofd, n, k, ret; + char *t, *raw, *unixheader, *all; + + t = estrstrdup(mbox.name, m->name); + raw = readfile(t, "raw", &n); + unixheader = readfile(t, "unixheader", &k); + if(raw==nil || unixheader==nil){ + fprint(2, "Mail: can't read %s: %r\n", t); + free(t); + return 0; + } + free(t); + + all = emalloc(n+k+1); + memmove(all, unixheader, k); + memmove(all+k, raw, n); + memmove(all+k+n, "\n", 1); + n = k+n+1; + free(unixheader); + free(raw); + ret = 1; + s = estrdup(s); + if(s[0] != '/') + s = egrow(estrdup(mailboxdir), "/", s); + ofd = open(s, OWRITE); + if(ofd < 0){ + fprint(2, "Mail: can't open %s: %r\n", s); + ret = 0; + }else if(seek(ofd, 0LL, 2)<0 || write(ofd, all, n)!=n){ + fprint(2, "Mail: save failed: can't write %s: %r\n", s); + ret = 0; + } + free(all); + close(ofd); + free(s); + return ret; +} + +int +mesgcommand(Message *m, char *cmd) +{ + char *s; + char *args[10]; + int ok, ret, nargs; + + s = cmd; + ret = 1; + nargs = tokenize(s, args, nelem(args)); + if(nargs == 0) + return 0; + if(strcmp(args[0], "Post") == 0){ + mesgsend(m); + goto Return; + } + if(strncmp(args[0], "Save", 4) == 0){ + if(m->isreply) + goto Return; + s = estrdup("\t[saved"); + if(nargs==1 || strcmp(args[1], "")==0){ + ok = mesgsave(m, "stored"); + }else{ + ok = mesgsave(m, args[1]); + s = eappend(s, " ", args[1]); + } + if(ok){ + s = egrow(s, "]", nil); + mesgmenumark(mbox.w, m->name, s); + } + free(s); + goto Return; + } + if(strcmp(args[0], "Reply")==0){ + if(nargs>=2 && strcmp(args[1], "all")==0) + mkreply(m, "Replyall", nil, nil, nil); + else + mkreply(m, "Reply", nil, nil, nil); + goto Return; + } + if(strcmp(args[0], "Q") == 0){ + s = winselection(m->w); /* will be freed by mkreply */ + if(nargs>=3 && strcmp(args[1], "Reply")==0 && strcmp(args[2], "all")==0) + mkreply(m, "QReplyall", nil, nil, s); + else + mkreply(m, "QReply", nil, nil, s); + goto Return; + } + if(strcmp(args[0], "Del") == 0){ + if(windel(m->w, 0)){ + chanfree(m->w->cevent); + free(m->w); + m->w = nil; + if(m->isreply) + delreply(m); + else{ + m->opened = 0; + m->tagposted = 0; + } + free(cmd); + threadexits(nil); + } + goto Return; + } + if(strcmp(args[0], "Delmesg") == 0){ + if(!m->isreply){ + mesgmenumarkdel(wbox, &mbox, m, 1); + free(cmd); /* mesgcommand might not return */ + mesgcommand(m, estrdup("Del")); + return 1; + } + goto Return; + } + if(strcmp(args[0], "UnDelmesg") == 0){ + if(!m->isreply && m->deleted) + mesgmenumarkundel(wbox, &mbox, m); + goto Return; + } +// if(strcmp(args[0], "Headers") == 0){ +// m->showheaders(); +// return True; +// } + + ret = 0; + + Return: + free(cmd); + return ret; +} + +void +mesgtagpost(Message *m) +{ + if(m->tagposted) + return; + wintagwrite(m->w, " Post", 5); + m->tagposted = 1; +} + +/* need to expand selection more than default word */ +#pragma varargck argpos eval 2 + +long +eval(Window *w, char *s, ...) +{ + char buf[64]; + va_list arg; + + va_start(arg, s); + vsnprint(buf, sizeof buf, s, arg); + va_end(arg); + + if(winsetaddr(w, buf, 1)==0) + return -1; + + if(pread(w->addr, buf, 24, 0) != 24) + return -1; + return strtol(buf, 0, 10); +} + +int +isemail(char *s) +{ + int nat; + + nat = 0; + for(; *s; s++) + if(*s == '@') + nat++; + else if(!isalpha(*s) && !isdigit(*s) && !strchr("_.-+/", *s)) + return 0; + return nat==1; +} + +char addrdelim[] = "/[ \t\\n<>()\\[\\]]/"; +char* +expandaddr(Window *w, Event *e) +{ + char *s; + long q0, q1; + + if(e->q0 != e->q1) /* cannot happen */ + return nil; + + q0 = eval(w, "#%d-%s", e->q0, addrdelim); + if(q0 == -1) /* bad char not found */ + q0 = 0; + else /* increment past bad char */ + q0++; + + q1 = eval(w, "#%d+%s", e->q0, addrdelim); + if(q1 < 0){ + q1 = eval(w, "$"); + if(q1 < 0) + return nil; + } + if(q0 >= q1) + return nil; + s = emalloc((q1-q0)*UTFmax+1); + winread(w, q0, q1, s); + return s; +} + +int +replytoaddr(Window *w, Message *m, Event *e, char *s) +{ + int did; + char *buf; + Plumbmsg *pm; + + buf = nil; + did = 0; + if(e->flag & 2){ + /* autoexpanded; use our own bigger expansion */ + buf = expandaddr(w, e); + if(buf == nil) + return 0; + s = buf; + } + if(isemail(s)){ + did = 1; + pm = emalloc(sizeof(Plumbmsg)); + pm->src = estrdup("Mail"); + pm->dst = estrdup("sendmail"); + pm->data = estrdup(s); + pm->ndata = -1; + if(m->subject && m->subject[0]){ + pm->attr = emalloc(sizeof(Plumbattr)); + pm->attr->name = estrdup("Subject"); + if(tolower(m->subject[0]) != 'r' || tolower(m->subject[1]) != 'e' || m->subject[2] != ':') + pm->attr->value = estrstrdup("Re: ", m->subject); + else + pm->attr->value = estrdup(m->subject); + pm->attr->next = nil; + } + if(plumbsend(plumbsendfd, pm) < 0) + fprint(2, "error writing plumb message: %r\n"); + plumbfree(pm); + } + free(buf); + return did; +} + + +void +mesgctl(void *v) +{ + Message *m; + Window *w; + Event *e, *eq, *e2, *ea; + int na, nopen, i, j; + char *os, *s, *t, *buf; + + m = v; + w = m->w; + threadsetname("mesgctl"); + proccreate(wineventproc, w, STACK); + for(;;){ + e = recvp(w->cevent); + switch(e->c1){ + default: + Unk: + print("unknown message %c%c\n", e->c1, e->c2); + break; + + case 'E': /* write to body; can't affect us */ + break; + + case 'F': /* generated by our actions; ignore */ + break; + + case 'K': /* type away; we don't care */ + case 'M': + switch(e->c2){ + case 'x': /* mouse only */ + case 'X': + ea = nil; + eq = e; + if(e->flag & 2){ + e2 = recvp(w->cevent); + eq = e2; + } + if(e->flag & 8){ + ea = recvp(w->cevent); + recvp(w->cevent); + na = ea->nb; + }else + na = 0; + if(eq->q1>eq->q0 && eq->nb==0){ + s = emalloc((eq->q1-eq->q0)*UTFmax+1); + winread(w, eq->q0, eq->q1, s); + }else + s = estrdup(eq->b); + if(na){ + t = emalloc(strlen(s)+1+na+1); + sprint(t, "%s %s", s, ea->b); + free(s); + s = t; + } + if(!mesgcommand(m, s)) /* send it back */ + winwriteevent(w, e); + break; + + case 'l': /* mouse only */ + case 'L': + buf = nil; + eq = e; + if(e->flag & 2){ + e2 = recvp(w->cevent); + eq = e2; + } + s = eq->b; + if(eq->q1>eq->q0 && eq->nb==0){ + buf = emalloc((eq->q1-eq->q0)*UTFmax+1); + winread(w, eq->q0, eq->q1, buf); + s = buf; + } + os = s; + nopen = 0; + do{ + /* skip mail box name if present */ + if(strncmp(s, mbox.name, strlen(mbox.name)) == 0) + s += strlen(mbox.name); + if(strstr(s, "body") != nil){ + /* strip any known extensions */ + for(i=0; exts[i].ext!=nil; i++){ + j = strlen(exts[i].ext); + if(strlen(s)>j && strcmp(s+strlen(s)-j, exts[i].ext)==0){ + s[strlen(s)-j] = '\0'; + break; + } + } + if(strlen(s)>5 && strcmp(s+strlen(s)-5, "/body")==0) + s[strlen(s)-4] = '\0'; /* leave / in place */ + } + nopen += mesgopen(&mbox, mbox.name, s, m, 0, nil); + while(*s!=0 && *s++!='\n') + ; + }while(*s); + if(nopen == 0 && e->c1 == 'L') + nopen += replytoaddr(w, m, e, os); + if(nopen == 0) + winwriteevent(w, e); + free(buf); + break; + + case 'I': /* modify away; we don't care */ + case 'D': + mesgtagpost(m); + /* fall through */ + case 'd': + case 'i': + break; + + default: + goto Unk; + } + } + } +} + +void +mesgline(Message *m, char *header, char *value) +{ + if(strlen(value) > 0) + Bprint(m->w->body, "%s: %s\n", header, value); +} + +int +isprintable(char *type) +{ + int i; + + for(i=0; goodtypes[i]!=nil; i++) + if(strcmp(type, goodtypes[i])==0) + return 1; + return 0; +} + +char* +ext(char *type) +{ + int i; + + for(i=0; exts[i].type!=nil; i++) + if(strcmp(type, exts[i].type)==0) + return exts[i].ext; + return ""; +} + +void +mimedisplay(Message *m, char *name, char *rootdir, Window *w, int fileonly) +{ + char *dest; + + if(strcmp(m->disposition, "file")==0 || strlen(m->filename)!=0){ + if(strlen(m->filename) == 0){ + dest = estrdup(m->name); + dest[strlen(dest)-1] = '\0'; + }else + dest = estrdup(m->filename); + if(m->filename[0] != '/') + dest = egrow(estrdup(home), "/", dest); + Bprint(w->body, "\tcp %s%sbody%s %q\n", rootdir, name, ext(m->type), dest); + free(dest); + }else if(!fileonly) + Bprint(w->body, "\tfile is %s%sbody%s\n", rootdir, name, ext(m->type)); +} + +void +printheader(char *dir, Biobuf *b, char **okheaders) +{ + char *s; + char *lines[100]; + int i, j, n; + + s = readfile(dir, "header", nil); + if(s == nil) + return; + n = getfields(s, lines, nelem(lines), 0, "\n"); + for(i=0; itype, "message/rfc822") != 0){ /* suppress headers of envelopes */ + if(strlen(m->from) > 0){ + Bprint(w->body, "From: %s\n", m->from); + mesgline(m, "Date", m->date); + mesgline(m, "To", m->to); + mesgline(m, "CC", m->cc); + mesgline(m, "Subject", m->subject); + printheader(dir, w->body, extraheaders); + }else{ + printheader(dir, w->body, okheaders); + printheader(dir, w->body, extraheaders); + } + Bprint(w->body, "\n"); + } + + if(m->level == 1 && m->recursed == 0){ + m->recursed = 1; + readmbox(m, rootdir, m->name); + } + if(m->head == nil){ /* single part message */ + if(strcmp(m->type, "text")==0 || strncmp(m->type, "text/", 5)==0){ + mimedisplay(m, m->name, rootdir, w, 1); + s = readbody(m->type, dir, &n); + winwritebody(w, s, n); + free(s); + }else + mimedisplay(m, m->name, rootdir, w, 0); + }else{ + /* multi-part message, either multipart/* or message/rfc822 */ + thisone = nil; + if(strcmp(m->type, "multipart/alternative") == 0){ + thisone = m->head; /* in case we can't find a good one */ + for(mp=m->head; mp!=nil; mp=mp->next) + if(isprintable(mp->type)){ + thisone = mp; + break; + } + } + for(mp=m->head; mp!=nil; mp=mp->next){ + if(thisone!=nil && mp!=thisone) + continue; + subdir = estrstrdup(dir, mp->name); + name = estrstrdup(file, mp->name); + /* skip first element in name because it's already in window name */ + if(mp != m->head) + Bprint(w->body, "\n===> %s (%s) [%s]\n", strchr(name, '/')+1, mp->type, mp->disposition); + if(strcmp(mp->type, "text")==0 || strncmp(mp->type, "text/", 5)==0){ + mimedisplay(mp, name, rootdir, w, 1); + printheader(subdir, w->body, okheaders); + printheader(subdir, w->body, extraheaders); + winwritebody(w, "\n", 1); + s = readbody(mp->type, subdir, &n); + winwritebody(w, s, n); + free(s); + }else{ + if(strncmp(mp->type, "multipart/", 10)==0 || strcmp(mp->type, "message/rfc822")==0){ + mp->w = w; + mesgload(mp, rootdir, name, w); + mp->w = nil; + }else + mimedisplay(mp, name, rootdir, w, 0); + } + free(name); + free(subdir); + } + } + free(dir); +} + +int +tokenizec(char *str, char **args, int max, char *splitc) +{ + int na; + int intok = 0; + + if(max <= 0) + return 0; + for(na=0; *str != '\0';str++){ + if(strchr(splitc, *str) == nil){ + if(intok) + continue; + args[na++] = str; + intok = 1; + }else{ + /* it's a separator/skip character */ + *str = '\0'; + if(intok){ + intok = 0; + if(na >= max) + break; + } + } + } + return na; +} + +Message* +mesglookup(Message *mbox, char *name, char *digest) +{ + int n; + Message *m; + char *t; + + if(digest){ + /* can find exactly */ + for(m=mbox->head; m!=nil; m=m->next) + if(strcmp(digest, m->digest) == 0) + break; + return m; + } + + n = strlen(name); + if(n == 0) + return nil; + if(name[n-1] == '/') + t = estrdup(name); + else + t = estrstrdup(name, "/"); + for(m=mbox->head; m!=nil; m=m->next) + if(strcmp(t, m->name) == 0) + break; + free(t); + return m; +} + +/* + * Find plumb port, knowing type is text, given file name (by extension) + */ +int +plumbportbysuffix(char *file) +{ + char *suf; + int i, nsuf, nfile; + + nfile = strlen(file); + for(i=0; ports[i].type!=nil; i++){ + suf = ports[i].suffix; + nsuf = strlen(suf); + if(nfile > nsuf) + if(cistrncmp(file+nfile-nsuf, suf, nsuf) == 0) + return i; + } + return 0; +} + +/* + * Find plumb port using type and file name (by extension) + */ +int +plumbport(char *type, char *file) +{ + int i; + + for(i=0; ports[i].type!=nil; i++) + if(strncmp(type, ports[i].type, strlen(ports[i].type)) == 0) + return i; + /* see if it's a text type */ + for(i=0; goodtypes[i]!=nil; i++) + if(strncmp(type, goodtypes[i], strlen(goodtypes[i])) == 0) + return plumbportbysuffix(file); + return -1; +} + +void +plumb(Message *m, char *dir) +{ + int i; + char *port; + Plumbmsg *pm; + + if(strlen(m->type) == 0) + return; + i = plumbport(m->type, m->filename); + if(i < 0) + fprint(2, "can't find destination for message subpart\n"); + else{ + port = ports[i].port; + pm = emalloc(sizeof(Plumbmsg)); + pm->src = estrdup("Mail"); + if(port) + pm->dst = estrdup(port); + else + pm->dst = nil; + pm->wdir = nil; + pm->type = estrdup("text"); + pm->ndata = -1; + pm->data = estrstrdup(dir, "body"); + pm->data = eappend(pm->data, "", ports[i].suffix); + if(plumbsend(plumbsendfd, pm) < 0) + fprint(2, "error writing plumb message: %r\n"); + plumbfree(pm); + } +} + +int +mesgopen(Message *mbox, char *dir, char *s, Message *mesg, int plumbed, char *digest) +{ + char *t, *u, *v; + Message *m; + char *direlem[10]; + int i, ndirelem, reuse; + + /* find white-space-delimited first word */ + for(t=s; *t!='\0' && !isspace(*t); t++) + ; + u = emalloc(t-s+1); + memmove(u, s, t-s); + /* separate it on slashes */ + ndirelem = tokenizec(u, direlem, nelem(direlem), "/"); + if(ndirelem <= 0){ + Error: + free(u); + return 0; + } + if(plumbed){ + write(wctlfd, "top", 3); + write(wctlfd, "current", 7); + } + /* open window for message */ + m = mesglookup(mbox, direlem[0], digest); + if(m == nil) + goto Error; + if(mesg!=nil && m!=mesg) /* string looked like subpart but isn't part of this message */ + goto Error; + if(m->opened == 0){ + if(m->w == nil){ + reuse = 0; + m->w = newwindow(); + }else{ + reuse = 1; + /* re-use existing window */ + if(winsetaddr(m->w, "0,$", 1)){ + if(m->w->data < 0) + m->w->data = winopenfile(m->w, "data"); + write(m->w->data, "", 0); + } + } + v = estrstrdup(mbox->name, m->name); + winname(m->w, v); + free(v); + if(!reuse){ + if(m->deleted) + wintagwrite(m->w, "Q Reply all UnDelmesg Save ", 2+6+4+10+5); + else + wintagwrite(m->w, "Q Reply all Delmesg Save ", 2+6+4+8+5); + } + threadcreate(mesgctl, m, STACK); + winopenbody(m->w, OWRITE); + mesgload(m, dir, m->name, m->w); + winclosebody(m->w); + winclean(m->w); + m->opened = 1; + if(ndirelem == 1){ + free(u); + return 1; + } + } + if(ndirelem == 1 && plumbport(m->type, m->filename) <= 0){ + /* make sure dot is visible */ + ctlprint(m->w->ctl, "show\n"); + return 0; + } + /* walk to subpart */ + dir = estrstrdup(dir, m->name); + for(i=1; iname, nil); + } + if(m != nil && plumbport(m->type, m->filename) > 0) + plumb(m, dir); + free(dir); + free(u); + return 1; +} + +void +rewritembox(Window *w, Message *mbox) +{ + Message *m, *next; + char *deletestr, *t; + int nopen; + + deletestr = estrstrdup("delete ", fsname); + + nopen = 0; + for(m=mbox->head; m!=nil; m=next){ + next = m->next; + if(m->deleted == 0) + continue; + if(m->opened){ + nopen++; + continue; + } + if(m->writebackdel){ + /* messages deleted by plumb message are not removed again */ + t = estrdup(m->name); + if(strlen(t) > 0) + t[strlen(t)-1] = '\0'; + deletestr = egrow(deletestr, " ", t); + } + mesgmenudel(w, mbox, m); + mesgdel(mbox, m); + } + if(write(mbox->ctlfd, deletestr, strlen(deletestr)) < 0) + fprint(2, "Mail: warning: error removing mail message files: %r\n"); + free(deletestr); + winselect(w, "0", 0); + if(nopen == 0) + winclean(w); + mbox->dirty = 0; +} + +/* name is a full file name, but it might not belong to us */ +Message* +mesglookupfile(Message *mbox, char *name, char *digest) +{ + int k, n; + + k = strlen(name); + n = strlen(mbox->name); + if(k==0 || strncmp(name, mbox->name, n) != 0){ +// fprint(2, "Mail: message %s not in this mailbox\n", name); + return nil; + } + return mesglookup(mbox, name+n, digest); +} diff --git a/acme/mail/src/mkfile b/acme/mail/src/mkfile new file mode 100644 index 000000000..aa6545d7d --- /dev/null +++ b/acme/mail/src/mkfile @@ -0,0 +1,30 @@ +syms + 8c -aa mesg.c reply.c util.c win.c >>syms + diff --git a/acme/mail/src/reply.c b/acme/mail/src/reply.c new file mode 100644 index 000000000..65841c570 --- /dev/null +++ b/acme/mail/src/reply.c @@ -0,0 +1,567 @@ +#include +#include +#include +#include +#include +#include +#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%s%s\n", lines[i][0]=='>'? "" : " ", lines[i]); + i++; + } + free(lines); + free(body); /* will free quotetext if non-nil */ + free(type); + return 1; +} + +void +mkreply(Message *m, char *label, char *to, Plumbattr *attr, char *quotetext) +{ + Message *r; + char *dir, *t; + int quotereply; + Plumbattr *a; + + quotereply = (label[0] == 'Q'); + r = emalloc(sizeof(Message)); + r->isreply = 1; + if(m != nil) + r->replyname = estrdup(m->name); + r->next = replies.head; + r->prev = nil; + if(replies.head != nil) + replies.head->prev = r; + replies.head = r; + if(replies.tail == nil) + replies.tail = r; + r->name = emalloc(strlen(mbox.name)+strlen(label)+10); + sprint(r->name, "%s%s%d", mbox.name, label, ++replyid); + r->w = newwindow(); + winname(r->w, r->name); + ctlprint(r->w->ctl, "cleartag"); + wintagwrite(r->w, "fmt Look Post Undo", 4+5+5+4); + r->tagposted = 1; + threadcreate(mesgctl, r, STACK); + winopenbody(r->w, OWRITE); + if(to!=nil && to[0]!='\0') + Bprint(r->w->body, "%s\n", to); + for(a=attr; a; a=a->next) + Bprint(r->w->body, "%s: %s\n", a->name, a->value); + dir = nil; + if(m != nil){ + dir = estrstrdup(mbox.name, m->name); + if(to == nil && attr == nil){ + /* Reply goes to replyto; Reply all goes to From and To and CC */ + if(strstr(label, "all") == nil) + Bprint(r->w->body, "To: %s\n", m->replyto); + else{ /* Replyall */ + if(strlen(m->from) > 0) + Bprint(r->w->body, "To: %s\n", m->from); + if(strlen(m->to) > 0) + Bprint(r->w->body, "To: %s\n", m->to); + if(strlen(m->cc) > 0) + Bprint(r->w->body, "CC: %s\n", m->cc); + } + } + if(strlen(m->subject) > 0){ + t = "Subject: Re: "; + if(strlen(m->subject) >= 3) + if(tolower(m->subject[0])=='r' && tolower(m->subject[1])=='e' && m->subject[2]==':') + t = "Subject: "; + Bprint(r->w->body, "%s%s\n", t, m->subject); + } + if(!quotereply){ + Bprint(r->w->body, "Include: %sraw\n", dir); + free(dir); + } + } + Bprint(r->w->body, "\n"); + if(m == nil) + Bprint(r->w->body, "\n"); + else if(quotereply){ + quote(m, r->w->body, dir, quotetext); + free(dir); + } + winclosebody(r->w); + if(m==nil && (to==nil || to[0]=='\0')) + winselect(r->w, "0", 0); + else + winselect(r->w, "$", 0); + winclean(r->w); + windormant(r->w); +} + +void +delreply(Message *m) +{ + if(m->next == nil) + replies.tail = m->prev; + else + m->next->prev = m->prev; + if(m->prev == nil) + replies.head = m->next; + else + m->prev->next = m->next; + mesgfreeparts(m); + free(m); +} + +/* copy argv to stack and free the incoming strings, so we don't leak argument vectors */ +void +buildargv(char **inargv, char *argv[NARGS+1], char args[NARGCHAR]) +{ + int i, n; + char *s, *a; + + s = args; + for(i=0; i= 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 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 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; ip[0] = p[0]; + e->p[1] = p[1]; + e->prog = "/bin/upas/marshal"; + e->argv = emalloc((1+1+2+4*natt+1)*sizeof(char*)); + e->argv[0] = estrdup("marshal"); + e->argv[1] = estrdup("-8"); + j = 2; + if(m->replyname){ + e->argv[j++] = estrdup("-R"); + e->argv[j++] = estrstrdup(mbox.name, m->replyname); + } + for(i=0; iargv[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 0){ + print2(p[1], ofd, "CC: "); + for(i=0; i 0){ + print2(p[1], ofd, "BCC: "); + for(i=0; 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 Include: %s\n", attlist[i]); + else + fprint(ofd, "=====> Attach: %s\n", attlist[i]); + } + if(ofd > 0) + write(ofd, "\n", 1); + + for(i=0; ireplyname != nil) + mesgmenumark(mbox.w, m->replyname, "\t[replied]"); + if(m->name[0] == '/') + s = estrdup(m->name); + else + s = estrstrdup(mbox.name, m->name); + s = egrow(s, "-R", nil); + winname(m->w, s); + free(s); + winclean(m->w); + /* mark message unopened because it's no longer the original message */ + m->opened = 0; +} diff --git a/acme/mail/src/util.c b/acme/mail/src/util.c new file mode 100644 index 000000000..c32de5feb --- /dev/null +++ b/acme/mail/src/util.c @@ -0,0 +1,105 @@ +#include +#include +#include +#include +#include +#include "dat.h" + +void* +emalloc(uint n) +{ + void *p; + + p = malloc(n); + if(p == nil) + error("can't malloc: %r"); + memset(p, 0, n); + setmalloctag(p, getcallerpc(&n)); + return p; +} + +void* +erealloc(void *p, uint n) +{ + p = realloc(p, n); + if(p == nil) + error("can't realloc: %r"); + setmalloctag(p, getcallerpc(&n)); + return p; +} + +char* +estrdup(char *s) +{ + char *t; + + t = emalloc(strlen(s)+1); + strcpy(t, s); + return t; +} + +char* +estrstrdup(char *s, char *t) +{ + char *u; + + u = emalloc(strlen(s)+strlen(t)+1); + strcpy(u, s); + strcat(u, t); + return u; +} + +char* +eappend(char *s, char *sep, char *t) +{ + char *u; + + if(t == nil) + u = estrstrdup(s, sep); + else{ + u = emalloc(strlen(s)+strlen(sep)+strlen(t)+1); + strcpy(u, s); + strcat(u, sep); + strcat(u, t); + } + free(s); + return u; +} + +char* +egrow(char *s, char *sep, char *t) +{ + s = eappend(s, sep, t); + free(t); + return s; +} + +void +error(char *fmt, ...) +{ + Fmt f; + char buf[64]; + va_list arg; + + fmtfdinit(&f, 2, buf, sizeof buf); + fmtprint(&f, "Mail: "); + va_start(arg, fmt); + fmtvprint(&f, fmt, arg); + va_end(arg); + fmtprint(&f, "\n"); + fmtfdflush(&f); + threadexitsall(fmt); +} + +void +ctlprint(int fd, char *fmt, ...) +{ + int n; + va_list arg; + + va_start(arg, fmt); + n = vfprint(fd, fmt, arg); + va_end(arg); + if(n <= 0) + error("control file write error: %r"); +} diff --git a/acme/mail/src/win.c b/acme/mail/src/win.c new file mode 100644 index 000000000..53d4ed122 --- /dev/null +++ b/acme/mail/src/win.c @@ -0,0 +1,341 @@ +#include +#include +#include +#include +#include +#include "dat.h" + +Window* +newwindow(void) +{ + char buf[12]; + Window *w; + + w = emalloc(sizeof(Window)); + w->ctl = open("/mnt/wsys/new/ctl", ORDWR|OCEXEC); + if(w->ctl<0 || read(w->ctl, buf, 12)!=12) + error("can't open window ctl file: %r"); + ctlprint(w->ctl, "noscroll\n"); + w->id = atoi(buf); + w->event = winopenfile(w, "event"); + w->addr = -1; /* will be opened when needed */ + w->body = nil; + w->data = -1; + w->cevent = chancreate(sizeof(Event*), 0); + return w; +} + +void +winsetdump(Window *w, char *dir, char *cmd) +{ + if(dir != nil) + ctlprint(w->ctl, "dumpdir %s\n", dir); + if(cmd != nil) + ctlprint(w->ctl, "dump %s\n", cmd); +} + +void +wineventproc(void *v) +{ + Window *w; + int i; + + w = v; + for(i=0; ; i++){ + if(i >= NEVENT) + i = 0; + wingetevent(w, &w->e[i]); + sendp(w->cevent, &w->e[i]); + } +} + +static int +winopenfile1(Window *w, char *f, int m) +{ + char buf[64]; + int fd; + + sprint(buf, "/mnt/wsys/%d/%s", w->id, f); + fd = open(buf, m|OCEXEC); + if(fd < 0) + error("can't open window file %s: %r", f); + return fd; +} + +int +winopenfile(Window *w, char *f) +{ + return winopenfile1(w, f, ORDWR); +} + +void +wintagwrite(Window *w, char *s, int n) +{ + int fd; + + fd = winopenfile(w, "tag"); + if(write(fd, s, n) != n) + error("tag write: %r"); + close(fd); +} + +void +winname(Window *w, char *s) +{ + ctlprint(w->ctl, "name %s\n", s); +} + +void +winopenbody(Window *w, int mode) +{ + char buf[256]; + + sprint(buf, "/mnt/wsys/%d/body", w->id); + w->body = Bopen(buf, mode|OCEXEC); + if(w->body == nil) + error("can't open window body file: %r"); +} + +void +winclosebody(Window *w) +{ + if(w->body != nil){ + Bterm(w->body); + w->body = nil; + } +} + +void +winwritebody(Window *w, char *s, int n) +{ + if(w->body == nil) + winopenbody(w, OWRITE); + if(Bwrite(w->body, s, n) != n) + error("write error to window: %r"); +} + +int +wingetec(Window *w) +{ + if(w->nbuf == 0){ + w->nbuf = read(w->event, w->buf, sizeof w->buf); + if(w->nbuf <= 0){ + /* probably because window has exited, and only called by wineventproc, so just shut down */ + threadexits(nil); + } + w->bufp = w->buf; + } + w->nbuf--; + return *w->bufp++; +} + +int +wingeten(Window *w) +{ + int n, c; + + n = 0; + while('0'<=(c=wingetec(w)) && c<='9') + n = n*10+(c-'0'); + if(c != ' ') + error("event number syntax"); + return n; +} + +int +wingeter(Window *w, char *buf, int *nb) +{ + Rune r; + int n; + + r = wingetec(w); + buf[0] = r; + n = 1; + if(r >= Runeself) { + while(!fullrune(buf, n)) + buf[n++] = wingetec(w); + chartorune(&r, buf); + } + *nb = n; + return r; +} + +void +wingetevent(Window *w, Event *e) +{ + int i, nb; + + e->c1 = wingetec(w); + e->c2 = wingetec(w); + e->q0 = wingeten(w); + e->q1 = wingeten(w); + e->flag = wingeten(w); + e->nr = wingeten(w); + if(e->nr > EVENTSIZE) + error("event string too long"); + e->nb = 0; + for(i=0; inr; i++){ + e->r[i] = wingeter(w, e->b+e->nb, &nb); + e->nb += nb; + } + e->r[e->nr] = 0; + e->b[e->nb] = 0; + if(wingetec(w) != '\n') + error("event syntax error"); +} + +void +winwriteevent(Window *w, Event *e) +{ + fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1); +} + +void +winread(Window *w, uint q0, uint q1, char *data) +{ + int m, n, nr; + char buf[256]; + + if(w->addr < 0) + w->addr = winopenfile(w, "addr"); + if(w->data < 0) + w->data = winopenfile(w, "data"); + m = q0; + while(m < q1){ + n = sprint(buf, "#%d", m); + if(write(w->addr, buf, n) != n) + error("error writing addr: %r"); + n = read(w->data, buf, sizeof buf); + if(n <= 0) + error("reading data: %r"); + nr = utfnlen(buf, n); + while(m+nr >q1){ + do; while(n>0 && (buf[--n]&0xC0)==0x80); + --nr; + } + if(n == 0) + break; + memmove(data, buf, n); + data += n; + *data = 0; + m += nr; + } +} + +void +windormant(Window *w) +{ + if(w->addr >= 0){ + close(w->addr); + w->addr = -1; + } + if(w->body != nil){ + Bterm(w->body); + w->body = nil; + } + if(w->data >= 0){ + close(w->data); + w->data = -1; + } +} + + +int +windel(Window *w, int sure) +{ + if(sure) + write(w->ctl, "delete\n", 7); + else if(write(w->ctl, "del\n", 4) != 4) + return 0; + /* event proc will die due to read error from event file */ + windormant(w); + close(w->ctl); + w->ctl = -1; + close(w->event); + w->event = -1; + return 1; +} + +void +winclean(Window *w) +{ + if(w->body) + Bflush(w->body); + ctlprint(w->ctl, "clean\n"); +} + +int +winsetaddr(Window *w, char *addr, int errok) +{ + if(w->addr < 0) + w->addr = winopenfile(w, "addr"); + if(write(w->addr, addr, strlen(addr)) < 0){ + if(!errok) + error("error writing addr(%s): %r", addr); + return 0; + } + return 1; +} + +int +winselect(Window *w, char *addr, int errok) +{ + if(winsetaddr(w, addr, errok)){ + ctlprint(w->ctl, "dot=addr\n"); + return 1; + } + return 0; +} + +char* +winreadbody(Window *w, int *np) /* can't use readfile because acme doesn't report the length */ +{ + char *s; + int m, na, n; + + if(w->body != nil) + winclosebody(w); + winopenbody(w, OREAD); + s = nil; + na = 0; + n = 0; + for(;;){ + if(na < n+512){ + na += 1024; + s = realloc(s, na+1); + } + m = Bread(w->body, s+n, na-n); + if(m <= 0) + break; + n += m; + } + s[n] = 0; + winclosebody(w); + *np = n; + return s; +} + +char* +winselection(Window *w) +{ + int fd, m, n; + char *buf; + char tmp[256]; + + fd = winopenfile1(w, "rdsel", OREAD); + if(fd < 0) + error("can't open rdsel: %r"); + n = 0; + buf = nil; + for(;;){ + m = read(fd, tmp, sizeof tmp); + if(m <= 0) + break; + buf = erealloc(buf, n+m+1); + memmove(buf+n, tmp, m); + n += m; + buf[n] = '\0'; + } + close(fd); + return buf; +} diff --git a/acme/mkfile b/acme/mkfile new file mode 100755 index 000000000..fc6fc9abe --- /dev/null +++ b/acme/mkfile @@ -0,0 +1,10 @@ + +#include +#include +#include +#include "win.h" +#include + +int canpost; +int debug; +int nshow = 20; + +int lo; /* next message to look at in dir */ +int hi; /* current hi message in dir */ +char *dir = "/mnt/news"; +char *group; +char *from; + +typedef struct Article Article; +struct Article { + Ref; + Article *prev; + Article *next; + Window *w; + int n; + int dead; + int dirtied; + int sayspost; + int headers; + int ispost; +}; + +Article *mlist; +Window *root; + +int +cistrncmp(char *a, char *b, int n) +{ + while(n-- > 0){ + if(tolower(*a++) != tolower(*b++)) + return -1; + } + return 0; +} + +int +cistrcmp(char *a, char *b) +{ + for(;;){ + if(tolower(*a) != tolower(*b++)) + return -1; + if(*a++ == 0) + break; + } + return 0; +} + +char* +skipwhite(char *p) +{ + while(isspace(*p)) + p++; + return p; +} + +int +gethi(void) +{ + Dir *d; + int hi; + + if((d = dirstat(dir)) == nil) + return -1; + hi = d->qid.vers; + free(d); + return hi; +} + +char* +fixfrom(char *s) +{ + char *r, *w; + + s = estrdup(s); + /* remove quotes */ + for(r=w=s; *r; r++) + if(*r != '"') + *w++ = *r; + *w = '\0'; + return s; +} + +char *day[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", }; +char *mon[] = { + "Jan", "Feb", "Mar", "Apr", + "May", "Jun", "Jul", "Aug", + "Sep", "Oct", "Nov", "Dec", +}; + +char* +fixdate(char *s) +{ + char *f[10], *m, *t, *wd, tmp[40]; + int d, i, j, nf, hh, mm; + + nf = tokenize(s, f, nelem(f)); + + wd = nil; + d = 0; + m = nil; + t = nil; + for(i=0; i=lo && ndata < 0) + w->data = winopenfile(w, "data"); + + fprint(w->ctl, "dirty\n"); + + winopenbody(w, OWRITE); + lo = adddir(w->body, hi, 0, nshow); + winclean(w); +} + +/* return 1 if handled, 0 otherwise */ +static int +iscmd(char *s, char *cmd) +{ + int len; + + len = strlen(cmd); + return strncmp(s, cmd, len)==0 && (s[len]=='\0' || s[len]==' ' || s[len]=='\t' || s[len]=='\n'); +} + +static char* +skip(char *s, char *cmd) +{ + s += strlen(cmd); + while(*s==' ' || *s=='\t' || *s=='\n') + s++; + return s; +} + +void +unlink(Article *m) +{ + if(mlist == m) + mlist = m->next; + + if(m->next) + m->next->prev = m->prev; + if(m->prev) + m->prev->next = m->next; + m->next = m->prev = nil; +} + +int mesgopen(char*); +int fillmesgwindow(int, Article*); +Article *newpost(void); +void replywindow(Article*); +void mesgpost(Article*); + +/* + * Depends on d.qid.vers being highest numbered message in dir. + */ +void +acmetimer(Article *m, Window *w) +{ + Biobuf *b; + Dir *d; + + assert(m==nil && w==root); + + if((d = dirstat(dir))==nil | hi==d->qid.vers){ + free(d); + return; + } + + if(w->data < 0) + w->data = winopenfile(w, "data"); + if(winsetaddr(w, "0", 0)) + write(w->data, "", 0); + + b = emalloc(sizeof(*b)); + Binit(b, w->data, OWRITE); + adddir(b, d->qid.vers, hi+1, d->qid.vers); + hi = d->qid.vers; + Bterm(b); + free(b); + free(d); + winselect(w, "0,.", 0); +} + +int +acmeload(Article*, Window*, char *s) +{ + int nopen; + +//fprint(2, "load %s\n", s); + + nopen = 0; + while(*s){ + /* skip directory name */ + if(strncmp(s, dir, strlen(dir))==0) + s += strlen(dir); + nopen += mesgopen(s); + if((s = strchr(s, '\n')) == nil) + break; + s = skip(s, ""); + } + return nopen; +} + +int +acmecmd(Article *m, Window *w, char *s) +{ + int n; + Biobuf *b; + +//fprint(2, "cmd %s\n", s); + + s = skip(s, ""); + + if(iscmd(s, "Del")){ + if(m == nil){ /* don't close dir until messages close */ + if(mlist != nil){ + ctlprint(mlist->w->ctl, "show\n"); + return 1; + } + if(windel(w, 0)) + threadexitsall(nil); + return 1; + }else{ + if(windel(w, 0)) + m->dead = 1; + return 1; + } + } + if(m==nil && iscmd(s, "More")){ + s = skip(s, "More"); + if(n = atoi(s)) + nshow = n; + + if(w->data < 0) + w->data = winopenfile(w, "data"); + winsetaddr(w, "$", 1); + + fprint(w->ctl, "dirty\n"); + + b = emalloc(sizeof(*b)); + Binit(b, w->data, OWRITE); + lo = adddir(b, lo, 0, nshow); + Bterm(b); + free(b); + winclean(w); + winsetaddr(w, ".,", 0); + } + if(m!=nil && !m->ispost && iscmd(s, "Headers")){ + m->headers = !m->headers; + fillmesgwindow(-1, m); + return 1; + } + if(iscmd(s, "Newpost")){ + m = newpost(); + winopenbody(m->w, OWRITE); + Bprint(m->w->body, "%s\nsubject: \n\n", group); + winclean(m->w); + winselect(m->w, "$", 0); + return 1; + } + if(m!=nil && !m->ispost && iscmd(s, "Reply")){ + replywindow(m); + return 1; + } +// if(m!=nil && iscmd(s, "Replymail")){ +// fprint(2, "no replymail yet\n"); +// return 1; +// } + if(iscmd(s, "Post")){ + mesgpost(m); + return 1; + } + return 0; +} + +void +acmeevent(Article *m, Window *w, Event *e) +{ + Event *ea, *e2, *eq; + char *s, *t, *buf; + int na; + //int n; + //ulong q0, q1; + + switch(e->c1){ /* origin of action */ + default: + Unknown: + fprint(2, "unknown message %c%c\n", e->c1, e->c2); + break; + + case 'T': /* bogus timer event! */ + acmetimer(m, w); + break; + + case 'F': /* generated by our actions; ignore */ + break; + + case 'E': /* write to body or tag; can't affect us */ + break; + + case 'K': /* type away; we don't care */ + if(m && (e->c2 == 'I' || e->c2 == 'D')){ + m->dirtied = 1; + if(!m->sayspost){ + wintagwrite(w, "Post ", 5); + m->sayspost = 1; + } + } + break; + + case 'M': /* mouse event */ + switch(e->c2){ /* type of action */ + case 'x': /* mouse: button 2 in tag */ + case 'X': /* mouse: button 2 in body */ + ea = nil; + //e2 = nil; + s = e->b; + if(e->flag & 2){ /* null string with non-null expansion */ + e2 = recvp(w->cevent); + if(e->nb==0) + s = e2->b; + } + if(e->flag & 8){ /* chorded argument */ + ea = recvp(w->cevent); /* argument */ + na = ea->nb; + recvp(w->cevent); /* ignore origin */ + }else + na = 0; + + /* append chorded arguments */ + if(na){ + t = emalloc(strlen(s)+1+na+1); + sprint(t, "%s %s", s, ea->b); + s = t; + } + /* if it's a known command, do it */ + /* if it's a long message, it can't be for us anyway */ + // DPRINT(2, "exec: %s\n", s); + if(!acmecmd(m, w, s)) /* send it back */ + winwriteevent(w, e); + if(na) + free(s); + break; + + case 'l': /* mouse: button 3 in tag */ + case 'L': /* mouse: button 3 in body */ + //buf = nil; + eq = e; + if(e->flag & 2){ + e2 = recvp(w->cevent); + eq = e2; + } + s = eq->b; + if(eq->q1>eq->q0 && eq->nb==0){ + buf = emalloc((eq->q1-eq->q0)*UTFmax+1); + winread(w, eq->q0, eq->q1, buf); + s = buf; + } + if(!acmeload(m, w, s)) + winwriteevent(w, e); + break; + + case 'i': /* mouse: text inserted in tag */ + case 'd': /* mouse: text deleted from tag */ + break; + + case 'I': /* mouse: text inserted in body */ + case 'D': /* mouse: text deleted from body */ + if(m == nil) + break; + + m->dirtied = 1; + if(!m->sayspost){ + wintagwrite(w, "Post ", 5); + m->sayspost = 1; + } + break; + + default: + goto Unknown; + } + } +} + +void +dirthread(void *v) +{ + Event *e; + Window *w; + + w = v; + while(e = recvp(w->cevent)) + acmeevent(nil, w, e); + + threadexitsall(nil); +} + +void +mesgthread(void *v) +{ + Event *e; + Article *m; + + m = v; + while(!m->dead && (e = recvp(m->w->cevent))) + acmeevent(m, m->w, e); + +//fprint(2, "msg %p exits\n", m); + unlink(m); + free(m->w); + free(m); + threadexits(nil); +} + +/* +Xref: news.research.att.com comp.os.plan9:7360 +Newsgroups: comp.os.plan9 +Path: news.research.att.com!batch0!uunet!ffx.uu.net!finch!news.mindspring.net!newsfeed.mathworks.com!fu-berlin.de!server1.netnews.ja.net!hgmp.mrc.ac.uk!pegasus.csx.cam.ac.uk!bath.ac.uk!ccsdhd +From: Stephen Adam +Subject: Future of Plan9 +Approved: plan9mod@bath.ac.uk +X-Newsreader: Microsoft Outlook Express 5.00.2014.211 +X-Mimeole: Produced By Microsoft MimeOLE V5.00.2014.211 +Sender: ccsdhd@bath.ac.uk (Dennis Davis) +Nntp-Posting-Date: Wed, 13 Dec 2000 21:28:45 EST +NNTP-Posting-Host: 203.54.121.233 +Organization: Telstra BigPond Internet Services (http://www.bigpond.com) +X-Date: Wed, 13 Dec 2000 20:43:37 +1000 +Lines: 12 +Message-ID: +References: <95pghu$3lf$1@news.fas.harvard.edu> <95ph36$3m9$1@news.fas.harvard.edu> <95png6$4ln$1@news.fas.harvard.edu> <95poqg$4rq$1@news.fas.harvard.edu> <95q40h$66c$2@news.fas.harvard.edu> <95qjhu$8ke$1@news.fas.harvard.edu> <95riue$bu2$1@news.fas.harvard.edu> <95rnar$cbu$1@news.fas.harvard.edu> +X-Msmail-Priority: Normal +X-Trace: newsfeeds.bigpond.com 976703325 203.54.121.233 (Wed, 13 Dec 2000 21:28:45 EST) +X-Priority: 3 +Date: Wed, 13 Dec 2000 10:49:50 GMT +*/ + +char *skipheader[] = +{ + "x-", + "path:", + "xref:", + "approved:", + "sender:", + "nntp-", + "organization:", + "lines:", + "message-id:", + "references:", + "reply-to:", + "mime-", + "content-", +}; + +int +fillmesgwindow(int fd, Article *m) +{ + Biobuf *b; + char *p, tmp[40]; + int i, inhdr, copy, xfd; + Window *w; + + xfd = -1; + if(fd == -1){ + sprint(tmp, "%d/article", m->n); + p = estrstrdup(dir, tmp); + if((xfd = open(p, OREAD)) < 0){ + free(p); + return 0; + } + free(p); + fd = xfd; + } + + w = m->w; + if(w->data < 0) + w->data = winopenfile(w, "data"); + if(winsetaddr(w, ",", 0)) + write(w->data, "", 0); + + winopenbody(m->w, OWRITE); + b = emalloc(sizeof(*b)); + Binit(b, fd, OREAD); + + inhdr = 1; + copy = 1; + while(p = Brdline(b, '\n')){ + if(Blinelen(b)==1) + inhdr = 0, copy=1; + if(inhdr && !isspace(p[0])){ + copy = 1; + if(!m->headers){ + if(cistrncmp(p, "from:", 5)==0){ + p[Blinelen(b)-1] = '\0'; + p = fixfrom(skip(p, "from:")); + Bprint(m->w->body, "From: %s\n", p); + free(p); + copy = 0; + continue; + } + for(i=0; iw->body, p, Blinelen(b)); + } + Bterm(b); + free(b); + winclean(m->w); + if(xfd != -1) + close(xfd); + return 1; +} + +Article* +newpost(void) +{ + Article *m; + char *p, tmp[40]; + static int nnew; + + m = emalloc(sizeof *m); + sprint(tmp, "Post%d", ++nnew); + p = estrstrdup(dir, tmp); + + m->w = newwindow(); + proccreate(wineventproc, m->w, STACK); + winname(m->w, p); + wintagwrite(m->w, "Post ", 5); + m->sayspost = 1; + m->ispost = 1; + threadcreate(mesgthread, m, STACK); + + if(mlist){ + m->next = mlist; + mlist->prev = m; + } + mlist = m; + return m; +} + +void +replywindow(Article *m) +{ + Biobuf *b; + char *p, *ep, *q, tmp[40]; + int fd, copy; + Article *reply; + + sprint(tmp, "%d/article", m->n); + p = estrstrdup(dir, tmp); + if((fd = open(p, OREAD)) < 0){ + free(p); + return; + } + free(p); + + reply = newpost(); + winopenbody(reply->w, OWRITE); + b = emalloc(sizeof(*b)); + Binit(b, fd, OREAD); + copy = 0; + while(p = Brdline(b, '\n')){ + if(Blinelen(b)==1) + break; + ep = p+Blinelen(b); + if(!isspace(*p)){ + copy = 0; + if(cistrncmp(p, "newsgroups:", 11)==0){ + for(q=p+11; *q!='\n'; q++) + if(*q==',') + *q = ' '; + copy = 1; + }else if(cistrncmp(p, "subject:", 8)==0){ + if(!strstr(p, " Re:") && !strstr(p, " RE:") && !strstr(p, " re:")){ + p = skip(p, "subject:"); + ep[-1] = '\0'; + Bprint(reply->w->body, "Subject: Re: %s\n", p); + }else + copy = 1; + }else if(cistrncmp(p, "message-id:", 11)==0){ + Bprint(reply->w->body, "References: "); + p += 11; + copy = 1; + } + } + if(copy) + Bwrite(reply->w->body, p, ep-p); + } + Bterm(b); + close(fd); + free(b); + Bprint(reply->w->body, "\n"); + winclean(reply->w); + winselect(reply->w, "$", 0); +} + +char* +skipbl(char *s, char *e) +{ + while(s < e){ + if(*s!=' ' && *s!='\t' && *s!=',') + break; + s++; + } + return s; +} + +char* +findbl(char *s, char *e) +{ + while(s < e){ + if(*s==' ' || *s=='\t' || *s==',') + break; + s++; + } + return s; +} + +/* + * comma-separate possibly blank-separated strings in line; e points before newline + */ +void +commas(char *s, char *e) +{ + char *t; + + /* may have initial blanks */ + s = skipbl(s, e); + while(s < e){ + s = findbl(s, e); + if(s == e) + break; + t = skipbl(s, e); + if(t == e) /* no more words */ + break; + /* patch comma */ + *s++ = ','; + while(s < t) + *s++ = ' '; + } +} +void +mesgpost(Article *m) +{ + Biobuf *b; + char *p, *ep; + int isfirst, ishdr, havegroup, havefrom; + + p = estrstrdup(dir, "post"); + if((b = Bopen(p, OWRITE)) == nil){ + fprint(2, "cannot open %s: %r\n", p); + free(p); + return; + } + free(p); + + winopenbody(m->w, OREAD); + ishdr = 1; + isfirst = 1; + havegroup = havefrom = 0; + while(p = Brdline(m->w->body, '\n')){ + ep = p+Blinelen(m->w->body); + if(ishdr && p+1==ep){ + if(!havegroup) + Bprint(b, "Newsgroups: %s\n", group); + if(!havefrom) + Bprint(b, "From: %s\n", from); + ishdr = 0; + } + if(ishdr){ + ep[-1] = '\0'; + if(isfirst && strchr(p, ':')==0){ /* group list */ + commas(p, ep); + Bprint(b, "newsgroups: %s\n", p); + havegroup = 1; + isfirst = 0; + continue; + } + if(cistrncmp(p, "newsgroup:", 10)==0){ + commas(skip(p, "newsgroup:"), ep); + Bprint(b, "newsgroups: %s\n", skip(p, "newsgroup:")); + havegroup = 1; + continue; + } + if(cistrncmp(p, "newsgroups:", 11)==0){ + commas(skip(p, "newsgroups:"), ep); + Bprint(b, "newsgroups: %s\n", skip(p, "newsgroups:")); + havegroup = 1; + continue; + } + if(cistrncmp(p, "from:", 5)==0) + havefrom = 1; + ep[-1] = '\n'; + } + Bwrite(b, p, ep-p); + } + winclosebody(m->w); + Bflush(b); + if(write(Bfildes(b), "", 0) == 0) + winclean(m->w); + else + fprint(2, "post: %r\n"); + Bterm(b); +} + +int +mesgopen(char *s) +{ + char *p, tmp[40]; + int fd, n; + Article *m; + + n = atoi(s); + if(n==0) + return 0; + + for(m=mlist; m; m=m->next){ + if(m->n == n){ + ctlprint(m->w->ctl, "show\n"); + return 1; + } + } + + sprint(tmp, "%d/article", n); + p = estrstrdup(dir, tmp); + if((fd = open(p, OREAD)) < 0){ + free(p); + return 0; + } + + m = emalloc(sizeof(*m)); + m->w = newwindow(); + m->n = n; + proccreate(wineventproc, m->w, STACK); + p[strlen(p)-strlen("article")] = '\0'; + winname(m->w, p); + if(canpost) + wintagwrite(m->w, "Reply ", 6); + wintagwrite(m->w, "Headers ", 8); + + free(p); + if(mlist){ + m->next = mlist; + mlist->prev = m; + } + mlist = m; + threadcreate(mesgthread, m, STACK); + + fillmesgwindow(fd, m); + close(fd); + windormant(m->w); + return 1; +} + +void +usage(void) +{ + fprint(2, "usage: News [-d /mnt/news] comp.os.plan9\n"); + exits("usage"); +} + +void +timerproc(void *v) +{ + Event e; + Window *w; + + memset(&e, 0, sizeof e); + e.c1 = 'T'; + w = v; + + for(;;){ + sleep(60*1000); + sendp(w->cevent, &e); + } +} + +char* +findfrom(void) +{ + char *p, *u; + Biobuf *b; + + u = getuser(); + if(u==nil) + return "glenda"; + + p = estrstrstrdup("/usr/", u, "/lib/newsfrom"); + b = Bopen(p, OREAD); + free(p); + if(b){ + p = Brdline(b, '\n'); + if(p){ + p[Blinelen(b)-1] = '\0'; + p = estrdup(p); + Bterm(b); + return p; + } + Bterm(b); + } + + p = estrstrstrdup("/mail/box/", u, "/headers"); + b = Bopen(p, OREAD); + free(p); + if(b){ + while(p = Brdline(b, '\n')){ + p[Blinelen(b)-1] = '\0'; + if(cistrncmp(p, "from:", 5)==0){ + p = estrdup(skip(p, "from:")); + Bterm(b); + return p; + } + } + Bterm(b); + } + + return u; +} + +void +threadmain(int argc, char **argv) +{ + char *p, *q; + Dir *d; + Window *w; + + ARGBEGIN{ + case 'D': + debug++; + break; + case 'd': + dir = EARGF(usage()); + break; + default: + usage(); + break; + }ARGEND + + if(argc != 1) + usage(); + + from = findfrom(); + + group = estrdup(argv[0]); /* someone will be cute */ + while(q=strchr(group, '/')) + *q = '.'; + + p = estrdup(argv[0]); + while(q=strchr(p, '.')) + *q = '/'; + p = estrstrstrdup(dir, "/", p); + cleanname(p); + + if((d = dirstat(p)) == nil){ /* maybe it is a new group */ + if((d = dirstat(dir)) == nil){ + fprint(2, "dirstat(%s) fails: %r\n", dir); + threadexitsall(nil); + } + if((d->mode&DMDIR)==0){ + fprint(2, "%s not a directory\n", dir); + threadexitsall(nil); + } + free(d); + if((d = dirstat(p)) == nil){ + fprint(2, "stat %s: %r\n", p); + threadexitsall(nil); + } + } + if((d->mode&DMDIR)==0){ + fprint(2, "%s not a directory\n", dir); + threadexitsall(nil); + } + free(d); + dir = estrstrdup(p, "/"); + + q = estrstrdup(dir, "post"); + canpost = access(q, AWRITE)==0; + + w = newwindow(); + root = w; + proccreate(wineventproc, w, STACK); + proccreate(timerproc, w, STACK); + + winname(w, dir); + if(canpost) + wintagwrite(w, "Newpost ", 8); + wintagwrite(w, "More ", 5); + dirwindow(w); + threadcreate(dirthread, w, STACK); + threadexits(nil); +} diff --git a/acme/news/src/util.c b/acme/news/src/util.c new file mode 100644 index 000000000..d3e6fdadc --- /dev/null +++ b/acme/news/src/util.c @@ -0,0 +1,106 @@ +#include +#include +#include +#include +#include "win.h" + +void* +emalloc(uint n) +{ + void *p; + + p = malloc(n); + if(p == nil) + error("can't malloc: %r"); + memset(p, 0, n); + return p; +} + +char* +estrdup(char *s) +{ + char *t; + + t = emalloc(strlen(s)+1); + strcpy(t, s); + return t; +} + +char* +estrstrdup(char *s, char *t) +{ + char *u; + + u = emalloc(strlen(s)+strlen(t)+1); + strcpy(u, s); + strcat(u, t); + return u; +} + +char* +estrstrstrdup(char *r, char *s, char *t) +{ + char *u; + + u = emalloc(strlen(r)+strlen(s)+strlen(t)+1); + strcpy(u, r); + strcat(u, s); + strcat(u, t); + return u; +} + +char* +eappend(char *s, char *sep, char *t) +{ + char *u; + + if(t == nil) + u = estrstrdup(s, sep); + else{ + u = emalloc(strlen(s)+strlen(sep)+strlen(t)+1); + strcpy(u, s); + strcat(u, sep); + strcat(u, t); + } + free(s); + return u; +} + +char* +egrow(char *s, char *sep, char *t) +{ + s = eappend(s, sep, t); + free(t); + return s; +} + +void +error(char *fmt, ...) +{ + va_list arg; + char buf[256]; + Fmt f; + + fmtfdinit(&f, 2, buf, sizeof buf); + fmtprint(&f, "%s: ", argv0); + va_start(arg, fmt); + fmtprint(&f, fmt, arg); + va_end(arg); + fmtprint(&f, "\n"); + fmtfdflush(&f); + threadexitsall(fmt); +} + +void +ctlprint(int fd, char *fmt, ...) +{ + int n; + va_list arg; + char buf[256]; + + va_start(arg, fmt); + n = vfprint(fd, fmt, arg); + va_end(arg); + if(n < 0) + error("control file write(%s) error: %r", buf); +} diff --git a/acme/news/src/win.c b/acme/news/src/win.c new file mode 100644 index 000000000..eebebd4e4 --- /dev/null +++ b/acme/news/src/win.c @@ -0,0 +1,324 @@ +#include +#include +#include +#include +#include "win.h" + +Window* +newwindow(void) +{ + char buf[12]; + Window *w; + + w = emalloc(sizeof(Window)); + w->ctl = open("/mnt/wsys/new/ctl", ORDWR|OCEXEC); + if(w->ctl<0 || read(w->ctl, buf, 12)!=12) + error("can't open window ctl file: %r"); + ctlprint(w->ctl, "noscroll\n"); + w->id = atoi(buf); + w->event = winopenfile(w, "event"); + w->addr = -1; /* will be opened when needed */ + w->body = nil; + w->data = -1; + w->cevent = chancreate(sizeof(Event*), 0); + if(w->cevent == nil) + error("cevent is nil: %r"); + return w; +} + +void +winsetdump(Window *w, char *dir, char *cmd) +{ + if(dir != nil) + ctlprint(w->ctl, "dumpdir %s\n", dir); + if(cmd != nil) + ctlprint(w->ctl, "dump %s\n", cmd); +} + +void +wineventproc(void *v) +{ + Window *w; + int i; + + threadsetname("wineventproc"); + w = v; + for(i=0; ; i++){ + if(i >= NEVENT) + i = 0; + wingetevent(w, &w->e[i]); + sendp(w->cevent, &w->e[i]); + } +} + +int +winopenfile(Window *w, char *f) +{ + char buf[64]; + int fd; + + sprint(buf, "/mnt/wsys/%d/%s", w->id, f); + fd = open(buf, ORDWR|OCEXEC); + if(fd < 0) + error("can't open window file %s: %r", f); + return fd; +} + +void +wintagwrite(Window *w, char *s, int n) +{ + int fd; + + fd = winopenfile(w, "tag"); + if(write(fd, s, n) != n) + error("tag write: %r"); + close(fd); +} + +void +winname(Window *w, char *s) +{ + ctlprint(w->ctl, "name %s\n", s); +} + +void +winopenbody(Window *w, int mode) +{ + char buf[256]; + + sprint(buf, "/mnt/wsys/%d/body", w->id); + w->body = Bopen(buf, mode|OCEXEC); + if(w->body == nil) + error("can't open window body file: %r"); +} + +void +winclosebody(Window *w) +{ + if(w->body != nil){ + Bterm(w->body); + w->body = nil; + } +} + +void +winwritebody(Window *w, char *s, int n) +{ + if(w->body == nil) + winopenbody(w, OWRITE); + if(Bwrite(w->body, s, n) != n) + error("write error to window: %r"); +} + +int +wingetec(Window *w) +{ + if(w->nbuf == 0){ + w->nbuf = read(w->event, w->buf, sizeof w->buf); + if(w->nbuf <= 0){ + /* probably because window has exited, and only called by wineventproc, so just shut down */ + threadexits(nil); + } + w->bufp = w->buf; + } + w->nbuf--; + return *w->bufp++; +} + +int +wingeten(Window *w) +{ + int n, c; + + n = 0; + while('0'<=(c=wingetec(w)) && c<='9') + n = n*10+(c-'0'); + if(c != ' ') + error("event number syntax"); + return n; +} + +int +wingeter(Window *w, char *buf, int *nb) +{ + Rune r; + int n; + + r = wingetec(w); + buf[0] = r; + n = 1; + if(r >= Runeself) { + while(!fullrune(buf, n)) + buf[n++] = wingetec(w); + chartorune(&r, buf); + } + *nb = n; + return r; +} + +void +wingetevent(Window *w, Event *e) +{ + int i, nb; + + e->c1 = wingetec(w); + e->c2 = wingetec(w); + e->q0 = wingeten(w); + e->q1 = wingeten(w); + e->flag = wingeten(w); + e->nr = wingeten(w); + if(e->nr > EVENTSIZE) + error("event string too long"); + e->nb = 0; + for(i=0; inr; 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; iaddr < 0) + w->addr = winopenfile(w, "addr"); + if(w->data < 0) + w->data = winopenfile(w, "data"); + m = q0; + while(m < q1){ + n = sprint(buf, "#%d", m); + if(write(w->addr, buf, n) != n) + error("error writing addr: %r"); + n = read(w->data, buf, sizeof buf); + if(n <= 0) + error("reading data: %r"); + nr = nrunes(buf, n); + while(m+nr >q1){ + do; while(n>0 && (buf[--n]&0xC0)==0x80); + --nr; + } + if(n == 0) + break; + memmove(data, buf, n); + data += n; + *data = 0; + m += nr; + } +} + +void +windormant(Window *w) +{ + if(w->addr >= 0){ + close(w->addr); + w->addr = -1; + } + if(w->body != nil){ + Bterm(w->body); + w->body = nil; + } + if(w->data >= 0){ + close(w->data); + w->data = -1; + } +} + + +int +windel(Window *w, int sure) +{ + if(sure) + write(w->ctl, "delete\n", 7); + else if(write(w->ctl, "del\n", 4) != 4) + return 0; + /* event proc will die due to read error from event file */ + windormant(w); + close(w->ctl); + w->ctl = -1; + close(w->event); + w->event = -1; + return 1; +} + +void +winclean(Window *w) +{ + if(w->body) + Bflush(w->body); + ctlprint(w->ctl, "clean\n"); +} + +int +winsetaddr(Window *w, char *addr, int errok) +{ + if(w->addr < 0) + w->addr = winopenfile(w, "addr"); + if(write(w->addr, addr, strlen(addr)) < 0){ + if(!errok) + error("error writing addr(%s): %r", addr); + return 0; + } + return 1; +} + +int +winselect(Window *w, char *addr, int errok) +{ + if(winsetaddr(w, addr, errok)){ + ctlprint(w->ctl, "dot=addr\n"); + return 1; + } + return 0; +} + +char* +winreadbody(Window *w, int *np) /* can't use readfile because acme doesn't report the length */ +{ + char *s; + int m, na, n; + + if(w->body != nil) + winclosebody(w); + winopenbody(w, OREAD); + s = nil; + na = 0; + n = 0; + for(;;){ + if(na < n+512){ + na += 1024; + s = realloc(s, na+1); + } + m = Bread(w->body, s+n, na-n); + if(m <= 0) + break; + n += m; + } + s[n] = 0; + winclosebody(w); + *np = n; + return s; +} diff --git a/acme/news/src/win.h b/acme/news/src/win.h new file mode 100644 index 000000000..414e6e31b --- /dev/null +++ b/acme/news/src/win.h @@ -0,0 +1,75 @@ +/* acme */ +typedef struct Event Event; +typedef struct Window Window; + +enum +{ + STACK = 8192, + EVENTSIZE = 256, + NEVENT = 5, +}; + +struct Event +{ + int c1; + int c2; + int q0; + int q1; + int flag; + int nb; + int nr; + char b[EVENTSIZE*UTFmax+1]; + Rune r[EVENTSIZE+1]; +}; + +struct Window +{ + /* file descriptors */ + int ctl; + int event; + int addr; + int data; + Biobuf *body; + + /* event input */ + char buf[512]; + char *bufp; + int nbuf; + Event e[NEVENT]; + + int dirtied; + int id; + int open; + Channel *cevent; /* chan(Event*) */ +}; + +extern Window* newwindow(void); +extern int winopenfile(Window*, char*); +extern void winopenbody(Window*, int); +extern void winclosebody(Window*); +extern void wintagwrite(Window*, char*, int); +extern void winname(Window*, char*); +extern void winwriteevent(Window*, Event*); +extern void winread(Window*, uint, uint, char*); +extern int windel(Window*, int); +extern void wingetevent(Window*, Event*); +extern void wineventproc(void*); +extern void winwritebody(Window*, char*, int); +extern void winclean(Window*); +extern int winselect(Window*, char*, int); +extern int winsetaddr(Window*, char*, int); +extern char* winreadbody(Window*, int*); +extern void windormant(Window*); +extern void winsetdump(Window*, char*, char*); + +extern char* readfile(char*, char*, int*); +extern void ctlprint(int, char*, ...); +extern void* emalloc(uint); +extern char* estrdup(char*); +extern char* estrstrdup(char*, char*); +extern char* estrstrstrdup(char*, char*, char*); +extern char* egrow(char*, char*, char*); +extern char* eappend(char*, char*, char*); +extern void error(char*, ...); +extern int tokenizec(char*, char**, int, char*); + diff --git a/acme/wiki/guide b/acme/wiki/guide new file mode 100644 index 000000000..a86332554 --- /dev/null +++ b/acme/wiki/guide @@ -0,0 +1,3 @@ +Local 9fs wiki +# Local wikifs /sys/lib/wiki +Wiki /mnt/wiki diff --git a/acme/wiki/src/awiki.h b/acme/wiki/src/awiki.h new file mode 100644 index 000000000..19fd61738 --- /dev/null +++ b/acme/wiki/src/awiki.h @@ -0,0 +1,114 @@ +#include +#include +#include +#include + +/* acme */ +typedef struct Event Event; +typedef struct Window Window; + +enum +{ + STACK = 8192, + EVENTSIZE = 256, + NEVENT = 5, +}; + +struct Event +{ + int c1; + int c2; + int q0; + int q1; + int flag; + int nb; + int nr; + char b[EVENTSIZE*UTFmax+1]; + Rune r[EVENTSIZE+1]; +}; + +struct Window +{ + /* file descriptors */ + int ctl; + int event; + int addr; + int data; + Biobuf *body; + + /* event input */ + char buf[512]; + char *bufp; + int nbuf; + Event e[NEVENT]; + + int warned; + int id; + int open; + Channel *cevent; /* chan(Event*) */ +}; + +extern Window* newwindow(void); +extern int winopenfile(Window*, char*); +extern void winopenbody(Window*, int); +extern void winclosebody(Window*); +extern void wintagwrite(Window*, char*, int); +extern void winname(Window*, char*); +extern void winwriteevent(Window*, Event*); +extern void winread(Window*, uint, uint, char*); +extern int windel(Window*, int); +extern void wingetevent(Window*, Event*); +extern void wineventproc(void*); +extern void winwritebody(Window*, char*, int); +extern void winclean(Window*); +extern int winisdirty(Window*); +extern int winselect(Window*, char*, int); +extern int winsetaddr(Window*, char*, int); +extern char* winreadbody(Window*, int*); +extern void windormant(Window*); +extern void winsetdump(Window*, char*, char*); + +extern char* readfile(char*, char*, int*); +extern void ctlprint(int, char*, ...); +extern void* emalloc(uint); +extern char* estrdup(char*); +extern char* estrstrdup(char*, char*); +extern char* egrow(char*, char*, char*); +extern char* eappend(char*, char*, char*); +extern void error(char*, ...); +extern int tokenizec(char*, char**, int, char*); + +typedef struct Treq Treq; +typedef struct Wiki Wiki; + +struct Treq { + char *title; + Channel *c; /* chan(int) */ +}; + +struct Wiki { + QLock; + int isnew; + int special; + char *arg; + char *addr; + int n; + int dead; + Window *win; + ulong time; + int linked; + Wiki *next; + Wiki *prev; +}; + +extern int debug; +extern int mapfd; +extern char *email; +extern char *dir; + +void wikinew(char*); +int wikiopen(char*, char*); +int wikiput(Wiki*); +void wikiget(Wiki*); +int wikidiff(Wiki*); + diff --git a/acme/wiki/src/main.c b/acme/wiki/src/main.c new file mode 100644 index 000000000..f76cafff9 --- /dev/null +++ b/acme/wiki/src/main.c @@ -0,0 +1,60 @@ +#include "awiki.h" + +int debug; +int mapfd; +char *email; +char *dir; + +void +usage(void) +{ + fprint(2, "usage: Wiki [-e email] [dir]\n"); + exits("usage"); +} + +void +threadmain(int argc, char **argv) +{ + char *s; + Dir *d; + + rfork(RFNAMEG); + ARGBEGIN{ + case 'D': + debug++; + break; + case 'e': + email = EARGF(usage()); + break; + default: + usage(); + break; + }ARGEND + + if(argc > 1) + usage(); + if(argc == 1) + dir = argv[0]; + else + dir = "/mnt/wiki"; + + if(chdir(dir) < 0){ + fprint(2, "chdir(%s) fails: %r\n", dir); + threadexitsall(nil); + } + + if((mapfd = open("map", ORDWR)) < 0){ + fprint(2, "open(map): %r\n"); + threadexitsall(nil); + } + + if((d = dirstat("1")) == nil){ + fprint(2, "dirstat(%s/1) fails: %r\n", dir); + threadexitsall(nil); + } + s = emalloc(strlen(d->name)+2); + strcpy(s, d->name); + strcat(s, "/"); + wikiopen(s, nil); + threadexits(nil); +} diff --git a/acme/wiki/src/mkfile b/acme/wiki/src/mkfile new file mode 100644 index 000000000..89431e702 --- /dev/null +++ b/acme/wiki/src/mkfile @@ -0,0 +1,14 @@ +linked) + return; + w->linked = 1; + w->prev = nil; + w->next = wlist; + if(wlist) + wlist->prev = w; + wlist = w; +} + +void +unlink(Wiki *w) +{ + if(!w->linked) + return; + w->linked = 0; + + if(w->next) + w->next->prev = w->prev; + if(w->prev) + w->prev->next = w->next; + else + wlist = w->next; + + w->next = nil; + w->prev = nil; +} + +void +wikiname(Window *w, char *name) +{ + char *p, *q; + + p = emalloc(strlen(dir)+1+strlen(name)+1+1); + strcpy(p, dir); + strcat(p, "/"); + strcat(p, name); + for(q=p; *q; q++) + if(*q==' ') + *q = '_'; + winname(w, p); + free(p); +} + +int +wikiput(Wiki *w) +{ + int fd, n; + char buf[1024], *p; + Biobuf *b; + + if((fd = open("new", ORDWR)) < 0){ + fprint(2, "Wiki: cannot open raw: %r\n"); + return -1; + } + + winopenbody(w->win, OREAD); + b = w->win->body; + if((p = Brdline(b, '\n'))==nil){ + Short: + winclosebody(w->win); + fprint(2, "Wiki: no data\n"); + close(fd); + return -1; + } + write(fd, p, Blinelen(b)); + + snprint(buf, sizeof buf, "D%lud\n", w->time); + if(email) + snprint(buf+strlen(buf), sizeof(buf)-strlen(buf), "A%s\n", email); + + if(Bgetc(b) == '#'){ + p = Brdline(b, '\n'); + if(p == nil) + goto Short; + snprint(buf+strlen(buf), sizeof(buf)-strlen(buf), "C%s\n", p); + } + write(fd, buf, strlen(buf)); + write(fd, "\n\n", 2); + + while((n = Bread(b, buf, sizeof buf)) > 0) + write(fd, buf, n); + winclosebody(w->win); + + werrstr(""); + if((n=write(fd, "", 0)) != 0){ + fprint(2, "Wiki commit %lud %d %d: %r\n", w->time, fd, n); + close(fd); + return -1; + } + seek(fd, 0, 0); + if((n = read(fd, buf, 300)) < 0){ + fprint(2, "Wiki readback: %r\n"); + close(fd); + return -1; + } + close(fd); + buf[n] = '\0'; + sprint(buf, "%s/", buf); + free(w->arg); + w->arg = estrdup(buf); + w->isnew = 0; + wikiget(w); + wikiname(w->win, w->arg); + return n; +} + +void +wikiget(Wiki *w) +{ + char *p; + int fd, normal; + Biobuf *bin; + + fprint(w->win->ctl, "dirty\n"); + + p = emalloc(strlen(w->arg)+8+1); + strcpy(p, w->arg); + normal = 1; + if(p[strlen(p)-1] == '/'){ + normal = 0; + strcat(p, "current"); + }else if(strlen(p)>8 && strcmp(p+strlen(p)-8, "/current")==0){ + normal = 0; + w->arg[strlen(w->arg)-7] = '\0'; + } + + if((fd = open(p, OREAD)) < 0){ + fprint(2, "Wiki: cannot read %s: %r\n", p); + winclean(w->win); + return; + } + free(p); + + winopenbody(w->win, OWRITE); + bin = emalloc(sizeof(*bin)); + Binit(bin, fd, OREAD); + + p = nil; + if(!normal){ + if((p = Brdline(bin, '\n')) == nil){ + fprint(2, "Wiki: cannot read title: %r\n"); + winclean(w->win); + close(fd); + free(bin); + return; + } + p[Blinelen(bin)-1] = '\0'; + } + /* clear window */ + if(w->win->data < 0) + w->win->data = winopenfile(w->win, "data"); + if(winsetaddr(w->win, ",", 0)) + write(w->win->data, "", 0); + + if(!normal) + Bprint(w->win->body, "%s\n\n", p); + + while(p = Brdline(bin, '\n')){ + p[Blinelen(bin)-1] = '\0'; + if(normal) + Bprint(w->win->body, "%s\n", p); + else{ + if(p[0]=='D') + w->time = strtoul(p+1, 0, 10); + else if(p[0]=='#') + Bprint(w->win->body, "%s\n", p+1); + } + } + winclean(w->win); + free(bin); + close(fd); +} + +static int +iscmd(char *s, char *cmd) +{ + int len; + + len = strlen(cmd); + return strncmp(s, cmd, len)==0 && (s[len]=='\0' || s[len]==' ' || s[len]=='\t' || s[len]=='\n'); +} + +static char* +skip(char *s, char *cmd) +{ + s += strlen(cmd); + while(*s==' ' || *s=='\t' || *s=='\n') + s++; + return s; +} + +int +wikiload(Wiki *w, char *arg) +{ + char *p, *q, *path, *addr; + int rv; + + p = nil; + if(arg[0] == '/') + path = arg; + else{ + p = emalloc(strlen(w->arg)+1+strlen(arg)+1); + strcpy(p, w->arg); + if(q = strrchr(p, '/')){ + ++q; + *q = '\0'; + }else + *p = '\0'; + strcat(p, arg); + cleanname(p); + path = p; + } + if(addr=strchr(path, ':')) + *addr++ = '\0'; + + rv = wikiopen(path, addr)==0; + free(p); + if(rv) + return 1; + return wikiopen(arg, 0)==0; +} + +/* return 1 if handled, 0 otherwise */ +int +wikicmd(Wiki *w, char *s) +{ + char *p; + s = skip(s, ""); + + if(iscmd(s, "Del")){ + if(windel(w->win, 0)) + w->dead = 1; + return 1; + } + if(iscmd(s, "New")){ + wikinew(skip(s, "New")); + return 1; + } + if(iscmd(s, "History")) + return wikiload(w, "history.txt"); + if(iscmd(s, "Diff")) + return wikidiff(w); + if(iscmd(s, "Get")){ + if(winisdirty(w->win) && !w->win->warned){ + w->win->warned = 1; + fprint(2, "%s/%s modified\n", dir, w->arg); + }else{ + w->win->warned = 0; + wikiget(w); + } + return 1; + } + if(iscmd(s, "Put")){ + if((p=strchr(w->arg, '/')) && p[1]!='\0') + fprint(2, "%s/%s is read-only\n", dir, w->arg); + else + wikiput(w); + return 1; + } + return 0; +} + +/* need to expand selection more than default word */ +static long +eval(Window *w, char *s, ...) +{ + char buf[64]; + va_list arg; + + va_start(arg, s); + vsnprint(buf, sizeof buf, s, arg); + va_end(arg); + + if(winsetaddr(w, buf, 1)==0) + return -1; + + if(pread(w->addr, buf, 24, 0) != 24) + return -1; + return strtol(buf, 0, 10); +} + +static int +getdot(Window *w, long *q0, long *q1) +{ + char buf[24]; + + ctlprint(w->ctl, "addr=dot\n"); + if(pread(w->addr, buf, 24, 0) != 24) + return -1; + *q0 = atoi(buf); + *q1 = atoi(buf+12); + return 0; +} + +static Event* +expand(Window *w, Event *e, Event *eacme) +{ + long q0, q1, x; + + if(getdot(w, &q0, &q1)==0 && q0 <= e->q0 && e->q0 <= q1){ + e->q0 = q0; + e->q1 = q1; + return e; + } + + q0 = eval(w, "#%lud-/\\[/", e->q0); + if(q0 < 0) + return eacme; + if(eval(w, "#%lud+/\\]/", q0) < e->q0) /* [ closes before us */ + return eacme; + q1 = eval(w, "#%lud+/\\]/", e->q1); + if(q1 < 0) + return eacme; + if((x=eval(w, "#%lud-/\\[/", q1))==-1 || x > e->q1) /* ] opens after us */ + return eacme; + e->q0 = q0+1; + e->q1 = q1; + return e; +} + +void +acmeevent(Wiki *wiki, Event *e) +{ + Event *ea, *e2, *eq; + Window *w; + char *s, *t, *buf; + int na; + + w = wiki->win; + switch(e->c1){ /* origin of action */ + default: + Unknown: + fprint(2, "unknown message %c%c\n", e->c1, e->c2); + break; + + case 'F': /* generated by our actions; ignore */ + break; + + case 'E': /* write to body or tag; can't affect us */ + break; + + case 'K': /* type away; we don't care */ + if(e->c2 == 'I' || e->c2 == 'D') + w->warned = 0; + break; + + case 'M': /* mouse event */ + switch(e->c2){ /* type of action */ + case 'x': /* mouse: button 2 in tag */ + case 'X': /* mouse: button 2 in body */ + ea = nil; + //e2 = nil; + s = e->b; + if(e->flag & 2){ /* null string with non-null expansion */ + e2 = recvp(w->cevent); + if(e->nb==0) + s = e2->b; + } + if(e->flag & 8){ /* chorded argument */ + ea = recvp(w->cevent); /* argument */ + na = ea->nb; + recvp(w->cevent); /* ignore origin */ + }else + na = 0; + + /* append chorded arguments */ + if(na){ + t = emalloc(strlen(s)+1+na+1); + sprint(t, "%s %s", s, ea->b); + s = t; + } + /* if it's a known command, do it */ + /* if it's a long message, it can't be for us anyway */ + // DPRINT(2, "exec: %s\n", s); + if(!wikicmd(wiki, s)) /* send it back */ + winwriteevent(w, e); + if(na) + free(s); + break; + + case 'l': /* mouse: button 3 in tag */ + case 'L': /* mouse: button 3 in body */ + //buf = nil; + eq = e; + if(e->flag & 2){ /* we do our own expansion for loads */ + e2 = recvp(w->cevent); + eq = expand(w, eq, e2); + } + s = eq->b; + if(eq->q1>eq->q0 && eq->nb==0){ + buf = emalloc((eq->q1-eq->q0)*UTFmax+1); + winread(w, eq->q0, eq->q1, buf); + s = buf; + } + if(!wikiload(wiki, s)) + winwriteevent(w, e); + break; + + case 'i': /* mouse: text inserted in tag */ + case 'd': /* mouse: text deleted from tag */ + break; + + case 'I': /* mouse: text inserted in body */ + case 'D': /* mouse: text deleted from body */ + w->warned = 0; + break; + + default: + goto Unknown; + } + } +} + +void +wikithread(void *v) +{ + char tmp[40]; + Event *e; + Wiki *w; + + w = v; + + if(w->isnew){ + sprint(tmp, "+new+%d", w->isnew); + wikiname(w->win, tmp); + if(w->arg){ + winopenbody(w->win, OWRITE); + Bprint(w->win->body, "%s\n\n", w->arg); + } + winclean(w->win); + }else if(!w->special){ + wikiget(w); + wikiname(w->win, w->arg); + if(w->addr) + winselect(w->win, w->addr, 1); + } + fprint(w->win->ctl, "menu\n"); + wintagwrite(w->win, "Get History Diff New", 4+8+4+4); + winclean(w->win); + + while(!w->dead && (e = recvp(w->win->cevent))) + acmeevent(w, e); + + windormant(w->win); + unlink(w); + free(w->win); + free(w->arg); + free(w); + threadexits(nil); +} + +int +wikiopen(char *arg, char *addr) +{ + Dir *d; + char *p; + Wiki *w; + +/* + if(arg==nil){ + if(write(mapfd, title, strlen(title)) < 0 + || seek(mapfd, 0, 0) < 0 || (n=read(mapfd, tmp, sizeof(tmp)-2)) < 0){ + fprint(2, "Wiki: no page '%s' found: %r\n", title); + return -1; + } + if(tmp[n-1] == '\n') + tmp[--n] = '\0'; + tmp[n++] = '/'; + tmp[n] = '\0'; + arg = tmp; + } +*/ + + /* replace embedded '\n' in links by ' ' */ + for(p=arg; *p; p++) + if(*p=='\n') + *p = ' '; + + if(strncmp(arg, dir, strlen(dir))==0 && arg[strlen(dir)]=='/' && arg[strlen(dir)+1]) + arg += strlen(dir)+1; + else if(arg[0] == '/') + return -1; + + if((d = dirstat(arg)) == nil) + return -1; + + if((d->mode&DMDIR) && arg[strlen(arg)-1] != '/'){ + p = emalloc(strlen(arg)+2); + strcpy(p, arg); + strcat(p, "/"); + arg = p; + }else if(!(d->mode&DMDIR) && arg[strlen(arg)-1]=='/'){ + arg = estrdup(arg); + arg[strlen(arg)-1] = '\0'; + }else + arg = estrdup(arg); + free(d); + + /* rewrite /current into / */ + if(strlen(arg) > 8 && strcmp(arg+strlen(arg)-8, "/current")==0) + arg[strlen(arg)-8+1] = '\0'; + + /* look for window already open */ + for(w=wlist; w; w=w->next){ + if(strcmp(w->arg, arg)==0){ + ctlprint(w->win->ctl, "show\n"); + return 0; + } + } + + w = emalloc(sizeof *w); + w->arg = arg; + w->addr = addr; + w->win = newwindow(); + link(w); + + proccreate(wineventproc, w->win, STACK); + threadcreate(wikithread, w, STACK); + return 0; +} + +void +wikinew(char *arg) +{ + static int n; + Wiki *w; + + w = emalloc(sizeof *w); + if(arg) + arg = estrdup(arg); + w->arg = arg; + w->win = newwindow(); + w->isnew = ++n; + proccreate(wineventproc, w->win, STACK); + threadcreate(wikithread, w, STACK); +} + +typedef struct Diffarg Diffarg; +struct Diffarg { + Wiki *w; + char *dir; +}; + +void +execdiff(void *v) +{ + char buf[64]; + Diffarg *a; + + a = v; + + rfork(RFFDG); + close(0); + open("/dev/null", OREAD); + sprint(buf, "/mnt/wsys/%d/body", a->w->win->id); + close(1); + open(buf, OWRITE); + close(2); + open(buf, OWRITE); + sprint(buf, "/mnt/wsys/%d", a->w->win->id); + bind(buf, "/dev", MBEFORE); + + procexecl(nil, "/acme/wiki/wiki.diff", "wiki.diff", a->dir, nil); +} + +int +wikidiff(Wiki *w) +{ + Diffarg *d; + char *p, *q, *r; + Wiki *nw; + + p = emalloc(strlen(w->arg)+10); + strcpy(p, w->arg); + if(q = strchr(p, '/')) + *q = '\0'; + r = estrdup(p); + strcat(p, "/+Diff"); + + nw = emalloc(sizeof *w); + nw->arg = p; + nw->win = newwindow(); + nw->special = 1; + + d = emalloc(sizeof(*d)); + d->w = nw; + d->dir = r; + wikiname(nw->win, p); + proccreate(wineventproc, nw->win, STACK); + proccreate(execdiff, d, STACK); + threadcreate(wikithread, nw, STACK); + return 1; +} + diff --git a/acme/wiki/src/win.c b/acme/wiki/src/win.c new file mode 100644 index 000000000..3eec1e9bd --- /dev/null +++ b/acme/wiki/src/win.c @@ -0,0 +1,341 @@ +#include "awiki.h" + +Window* +newwindow(void) +{ + char buf[12]; + Window *w; + + w = emalloc(sizeof(Window)); + w->ctl = open("/mnt/wsys/new/ctl", ORDWR|OCEXEC); + if(w->ctl<0 || read(w->ctl, buf, 12)!=12) + error("can't open window ctl file: %r"); + ctlprint(w->ctl, "noscroll\n"); + w->id = atoi(buf); + w->event = winopenfile(w, "event"); + w->addr = -1; /* will be opened when needed */ + w->body = nil; + w->data = -1; + w->cevent = chancreate(sizeof(Event*), 0); + if(w->cevent == nil) + error("cevent is nil: %r"); + return w; +} + +void +winsetdump(Window *w, char *dir, char *cmd) +{ + if(dir != nil) + ctlprint(w->ctl, "dumpdir %s\n", dir); + if(cmd != nil) + ctlprint(w->ctl, "dump %s\n", cmd); +} + +void +wineventproc(void *v) +{ + Window *w; + int i; + + threadsetname("wineventproc"); + w = v; + for(i=0; ; i++){ + if(i >= NEVENT) + i = 0; + wingetevent(w, &w->e[i]); + sendp(w->cevent, &w->e[i]); + } +} + +int +winopenfile(Window *w, char *f) +{ + char buf[64]; + int fd; + + sprint(buf, "/mnt/wsys/%d/%s", w->id, f); + fd = open(buf, ORDWR|OCEXEC); + if(fd < 0) + error("can't open window file %s: %r", f); + return fd; +} + +void +wintagwrite(Window *w, char *s, int n) +{ + int fd; + + fd = winopenfile(w, "tag"); + if(write(fd, s, n) != n) + error("tag write: %r"); + close(fd); +} + +void +winname(Window *w, char *s) +{ + ctlprint(w->ctl, "name %s\n", s); +} + +void +winopenbody(Window *w, int mode) +{ + char buf[256]; + + sprint(buf, "/mnt/wsys/%d/body", w->id); + w->body = Bopen(buf, mode|OCEXEC); + if(w->body == nil) + error("can't open window body file: %r"); +} + +void +winclosebody(Window *w) +{ + if(w->body != nil){ + Bterm(w->body); + w->body = nil; + } +} + +void +winwritebody(Window *w, char *s, int n) +{ + if(w->body == nil) + winopenbody(w, OWRITE); + if(Bwrite(w->body, s, n) != n) + error("write error to window: %r"); +} + +int +wingetec(Window *w) +{ + if(w->nbuf == 0){ + w->nbuf = read(w->event, w->buf, sizeof w->buf); + if(w->nbuf <= 0){ + /* probably because window has exited, and only called by wineventproc, so just shut down */ + threadexits(nil); + } + w->bufp = w->buf; + } + w->nbuf--; + return *w->bufp++; +} + +int +wingeten(Window *w) +{ + int n, c; + + n = 0; + while('0'<=(c=wingetec(w)) && c<='9') + n = n*10+(c-'0'); + if(c != ' ') + error("event number syntax"); + return n; +} + +int +wingeter(Window *w, char *buf, int *nb) +{ + Rune r; + int n; + + r = wingetec(w); + buf[0] = r; + n = 1; + if(r >= Runeself) { + while(!fullrune(buf, n)) + buf[n++] = wingetec(w); + chartorune(&r, buf); + } + *nb = n; + return r; +} + +void +wingetevent(Window *w, Event *e) +{ + int i, nb; + + e->c1 = wingetec(w); + e->c2 = wingetec(w); + e->q0 = wingeten(w); + e->q1 = wingeten(w); + e->flag = wingeten(w); + e->nr = wingeten(w); + if(e->nr > EVENTSIZE) + error("event string too long"); + e->nb = 0; + for(i=0; inr; 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; iaddr < 0) + w->addr = winopenfile(w, "addr"); + if(w->data < 0) + w->data = winopenfile(w, "data"); + m = q0; + while(m < q1){ + n = sprint(buf, "#%d", m); + if(write(w->addr, buf, n) != n) + error("error writing addr: %r"); + n = read(w->data, buf, sizeof buf); + if(n <= 0) + error("reading data: %r"); + nr = nrunes(buf, n); + while(m+nr >q1){ + do; while(n>0 && (buf[--n]&0xC0)==0x80); + --nr; + } + if(n == 0) + break; + memmove(data, buf, n); + data += n; + *data = 0; + m += nr; + } +} + +void +windormant(Window *w) +{ + if(w->addr >= 0){ + close(w->addr); + w->addr = -1; + } + if(w->body != nil){ + Bterm(w->body); + w->body = nil; + } + if(w->data >= 0){ + close(w->data); + w->data = -1; + } +} + + +int +windel(Window *w, int sure) +{ + if(sure) + write(w->ctl, "delete\n", 7); + else if(write(w->ctl, "del\n", 4) != 4) + return 0; + /* event proc will die due to read error from event file */ + windormant(w); + close(w->ctl); + w->ctl = -1; + close(w->event); + w->event = -1; + return 1; +} + +void +winclean(Window *w) +{ + if(w->body) + Bflush(w->body); + ctlprint(w->ctl, "clean\n"); +} + +int +winisdirty(Window *w) +{ + char m; + + if (seek(w->ctl, 4*(11+1) + 10, 0) < 0) + error("control file seek error: %r"); + + if(read(w->ctl, &m, 1) != 1) + error("control file read error: %r"); + + if (m == '0') + return 0; + else if (m == '1') + return 1; + else + error("can't parse ismodified field: %c", m); + return 1; // better safe than sorry + +} + +int +winsetaddr(Window *w, char *addr, int errok) +{ + if(w->addr < 0) + w->addr = winopenfile(w, "addr"); + if(write(w->addr, addr, strlen(addr)) < 0){ + if(!errok) + error("error writing addr(%s): %r", addr); + return 0; + } + return 1; +} + +int +winselect(Window *w, char *addr, int errok) +{ + if(winsetaddr(w, addr, errok)){ + ctlprint(w->ctl, "dot=addr\n"); + return 1; + } + return 0; +} + +char* +winreadbody(Window *w, int *np) /* can't use readfile because acme doesn't report the length */ +{ + char *s; + int m, na, n; + + if(w->body != nil) + winclosebody(w); + winopenbody(w, OREAD); + s = nil; + na = 0; + n = 0; + for(;;){ + if(na < n+512){ + na += 1024; + s = realloc(s, na+1); + } + m = Bread(w->body, s+n, na-n); + if(m <= 0) + break; + n += m; + } + s[n] = 0; + winclosebody(w); + *np = n; + return s; +} diff --git a/acme/wiki/wiki.diff b/acme/wiki/wiki.diff new file mode 100755 index 000000000..6214571b9 --- /dev/null +++ b/acme/wiki/wiki.diff @@ -0,0 +1,27 @@ +#!/bin/rc + +rfork n +cd $1 +*=(`{ls -drp [0-9]*}) + +while(! ~ $#* 0 1){ + diff -n $2/index.txt $1/index.txt | awk -F'[\/ :]' ' + $1 ~/^[0-9]+$/ { + getA = "cat "$5"/current | sed -n -e ''1d; /^A/s/^A//p; /^#/q''" + getA | getline A; close getA + $1 = t2d($1) + $5 = t2d($5) + print "\n" A ":\n" $1":"$3" "$4" "$5":"$7 + next + } + { print } + + function t2d(t) { + c = "date "t; c|getline l; close c + split(l, a, "[ :]+") + return a[1]" "a[2]" "a[3]" "a[4]":"a[5]" "a[8]"("t")" + }' + shift +} + +echo clean >/dev/ctl >[2]/dev/null diff --git a/alpha/bin/.dummy b/alpha/bin/.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/alpha/bin/ip/.dummy b/alpha/bin/ip/.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/amd64/bin/.dummy b/amd64/bin/.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/amd64/bin/ip/.dummy b/amd64/bin/ip/.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/amd64/lib/.dummy b/amd64/lib/.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/arm/bin/.dummy b/arm/bin/.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/arm/bin/ip/.dummy b/arm/bin/ip/.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/arm/lib/.dummy b/arm/lib/.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/mips/bin/.dummy b/mips/bin/.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/mips/lib/.dummy b/mips/lib/.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/power/bin/.dummy b/power/bin/.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/power/bin/ip/.dummy b/power/bin/ip/.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/power/lib/.dummy b/power/lib/.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/power64/bin/.dummy b/power64/bin/.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/power64/bin/ip/.dummy b/power64/bin/ip/.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/power64/lib/.dummy b/power64/lib/.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/sparc/bin/.dummy b/sparc/bin/.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/sparc/bin/ip/.dummy b/sparc/bin/ip/.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/sparc/lib/.dummy b/sparc/lib/.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/sparc64/bin/.dummy b/sparc64/bin/.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/sparc64/bin/ip/.dummy b/sparc64/bin/ip/.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/sparc64/lib/.dummy b/sparc64/lib/.dummy deleted file mode 100644 index e69de29bb..000000000