summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-30 21:29:14 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-30 21:29:14 +0000
commit0e96539ff7cff23233d3f0a64bb285b385a3a1f4 (patch)
tree5e5363878c35855f236b12cd85ae13d9102b42bc
parent6e425a9de8c003b5a733621a6b6730ec3cc902b8 (diff)
2006121920061220
-rw-r--r--FreeBSD/386/bin/mkextbin0 -> 68041 bytes
-rw-r--r--MacOSX/386/bin/data2cbin0 -> 24560 bytes
-rw-r--r--MacOSX/386/bin/mkbin0 -> 66696 bytes
-rw-r--r--MacOSX/386/bin/mkextbin0 -> 34036 bytes
-rw-r--r--MacOSX/386/bin/yaccbin0 -> 56644 bytes
-rw-r--r--MacOSX/386/include/fpuctl.h76
-rw-r--r--MacOSX/386/include/lib9.h507
-rw-r--r--MacOSX/power/bin/mkextbin0 -> 41048 bytes
-rw-r--r--appl/cmd/ar.b856
-rw-r--r--appl/cmd/cddb.b257
-rw-r--r--appl/cmd/lookman.b250
-rw-r--r--appl/cmd/man2html.b1328
-rw-r--r--appl/cmd/shutdown.b72
-rw-r--r--appl/lib/csv.b86
-rw-r--r--appl/lib/rfc822.b561
-rw-r--r--appl/lib/w3c/uris.b320
-rw-r--r--appl/spree/man/styxservers-nametree.man2180
-rw-r--r--appl/spree/man/styxservers.man2902
-rw-r--r--lib/mimetype157
-rw-r--r--lib/mk/binds2
-rw-r--r--lib/mk/mkconfig28
-rw-r--r--lib/mk/mksubdirs13
-rw-r--r--lib9/getcallerpc-Linux-arm.S1
-rw-r--r--lib9/getcallerpc-MacOSX-386.s8
-rw-r--r--mkfiles/mkfile-Linux-arm27
-rw-r--r--mkfiles/mkfile-MacOSX-38641
-rw-r--r--module/csv.m8
-rw-r--r--module/ida.m24
-rw-r--r--module/rfc822.m68
-rw-r--r--module/uris.m36
-rw-r--r--utils/cvbit/cvbit.c86
-rw-r--r--utils/cvbit/mkfile15
32 files changed, 4505 insertions, 1404 deletions
diff --git a/FreeBSD/386/bin/mkext b/FreeBSD/386/bin/mkext
new file mode 100644
index 00000000..050bf695
--- /dev/null
+++ b/FreeBSD/386/bin/mkext
Binary files differ
diff --git a/MacOSX/386/bin/data2c b/MacOSX/386/bin/data2c
new file mode 100644
index 00000000..0de8f7b8
--- /dev/null
+++ b/MacOSX/386/bin/data2c
Binary files differ
diff --git a/MacOSX/386/bin/mk b/MacOSX/386/bin/mk
new file mode 100644
index 00000000..96f7f083
--- /dev/null
+++ b/MacOSX/386/bin/mk
Binary files differ
diff --git a/MacOSX/386/bin/mkext b/MacOSX/386/bin/mkext
new file mode 100644
index 00000000..8eca76ca
--- /dev/null
+++ b/MacOSX/386/bin/mkext
Binary files differ
diff --git a/MacOSX/386/bin/yacc b/MacOSX/386/bin/yacc
new file mode 100644
index 00000000..a315ba8a
--- /dev/null
+++ b/MacOSX/386/bin/yacc
Binary files differ
diff --git a/MacOSX/386/include/fpuctl.h b/MacOSX/386/include/fpuctl.h
new file mode 100644
index 00000000..8389f6ee
--- /dev/null
+++ b/MacOSX/386/include/fpuctl.h
@@ -0,0 +1,76 @@
+/*
+ * Linux 386 fpu support
+ * Mimic Plan9 floating point support
+ */
+
+static void
+setfcr(ulong fcr)
+{
+ __asm__( "xorb $0x3f, %%al\n\t"
+ "pushw %%ax\n\t"
+ "fwait\n\t"
+ "fldcw (%%esp)\n\t"
+ "popw %%ax\n\t"
+ : /* no output */
+ : "al" (fcr)
+ );
+}
+
+static ulong
+getfcr(void)
+{
+ ulong fcr = 0;
+
+ __asm__( "pushl %%eax\n\t"
+ "fwait\n\t"
+ "fstcw (%%esp)\n\t"
+ "popl %%eax\n\t"
+ "xorb $0x3f, %%al\n\t"
+ : "=a" (fcr)
+ : "eax" (fcr)
+ );
+ return fcr;
+}
+
+static ulong
+getfsr(void)
+{
+ ulong fsr = -1;
+
+ __asm__( "fwait\n\t"
+ "fstsw (%%eax)\n\t"
+ "movl (%%eax), %%eax\n\t"
+ "andl $0xffff, %%eax\n\t"
+ : "=a" (fsr)
+ : "eax" (&fsr)
+ );
+ return fsr;
+}
+
+static void
+setfsr(ulong fsr)
+{
+ __asm__("fclex\n\t");
+}
+
+/* FCR */
+#define FPINEX (1<<5)
+#define FPUNFL ((1<<4)|(1<<1))
+#define FPOVFL (1<<3)
+#define FPZDIV (1<<2)
+#define FPINVAL (1<<0)
+#define FPRNR (0<<10)
+#define FPRZ (3<<10)
+#define FPRPINF (2<<10)
+#define FPRNINF (1<<10)
+#define FPRMASK (3<<10)
+#define FPPEXT (3<<8)
+#define FPPSGL (0<<8)
+#define FPPDBL (2<<8)
+#define FPPMASK (3<<8)
+/* FSR */
+#define FPAINEX FPINEX
+#define FPAOVFL FPOVFL
+#define FPAUNFL FPUNFL
+#define FPAZDIV FPZDIV
+#define FPAINVAL FPINVAL
diff --git a/MacOSX/386/include/lib9.h b/MacOSX/386/include/lib9.h
new file mode 100644
index 00000000..69bbbb95
--- /dev/null
+++ b/MacOSX/386/include/lib9.h
@@ -0,0 +1,507 @@
+/*
+ * Based on FreeBSD lib9.h
+ * Copyright © 1998, 1999 Lucent Technologies Inc. All rights reserved.
+ * Revisions Copyright © 1999, 2002 Vita Nuova Limited. All rights reserved.
+ * Revisions Copyright © 2002, 2003 Corpus Callosum Corporation. All rights reserved.
+ */
+
+/* define _BSD_SOURCE to use ISO C, POSIX, and 4.4BSD things. */
+#ifndef _BSD_SOURCE
+#define _BSD_SOURCE
+#endif
+#define _LARGEFILE64_SOURCE 1
+#define _FILE_OFFSET_BITS 64
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+// #include "math.h"
+#include <fcntl.h>
+#include <setjmp.h>
+#include <float.h>
+#include <time.h>
+
+#define nil ((void*)0)
+
+// typedef unsigned short ushort;
+typedef unsigned char uchar;
+typedef unsigned long ulong;
+// typedef unsigned int uint;
+typedef signed char schar;
+typedef long long vlong;
+typedef unsigned long long uvlong;
+typedef ushort Rune;
+typedef unsigned int u32int;
+typedef unsigned int mpdigit; /* for /sys/include/mp.h */
+typedef unsigned short u16int;
+typedef unsigned char u8int;
+typedef unsigned long uintptr;
+
+/* handle conflicts with host os libs */
+#define getwd infgetwd
+#define scalb infscalb
+#define div infdiv
+#define panic infpanic
+#define rint infrint
+#define rcmd infrcmd
+#undef isnan
+#define pow10 infpow10
+
+#ifndef EMU
+typedef struct Proc Proc;
+#endif
+
+/*
+ * math module dtoa
+ */
+#include <machine/endian.h>
+#define __LITTLE_ENDIAN
+
+#define USED(x) if(x){}else{}
+#define SET(x)
+
+#define nelem(x) (sizeof(x)/sizeof((x)[0]))
+#define offsetof(s, m) (ulong)(&(((s*)0)->m))
+#define assert(x) if(x){}else _assert("x")
+
+/*
+ * mem and string routines are declared by ANSI/POSIX files above
+ */
+
+extern char* strecpy(char*, char*, char*);
+extern char* strdup(const char*);
+extern int cistrncmp(char*, char*, int);
+extern int cistrcmp(char*, char*);
+extern char* cistrstr(char*, char*);
+extern int tokenize(char*, char**, int);
+extern vlong strtoll(const char*, char**, int);
+
+enum
+{
+ UTFmax = 3, /* maximum bytes per rune */
+ Runesync = 0x80, /* cannot represent part of a UTF sequence (<) */
+ Runeself = 0x80, /* rune and UTF sequences are the same (<) */
+ Runeerror = 0x80 /* decoding error in UTF */
+};
+
+/*
+ * rune routines
+ */
+extern int runetochar(char*, Rune*);
+extern int chartorune(Rune*, char*);
+extern int runelen(long);
+extern int runenlen(Rune*, int);
+extern int fullrune(char*, int);
+extern int utflen(char*);
+extern int utfnlen(char*, long);
+extern char* utfrune(char*, long);
+extern char* utfrrune(char*, long);
+extern char* utfutf(char*, char*);
+extern char* utfecpy(char*, char*, char*);
+
+extern Rune* runestrcat(Rune*, Rune*);
+extern Rune* runestrchr(Rune*, Rune);
+extern int runestrcmp(Rune*, Rune*);
+extern Rune* runestrcpy(Rune*, Rune*);
+extern Rune* runestrncpy(Rune*, Rune*, long);
+extern Rune* runestrecpy(Rune*, Rune*, Rune*);
+extern Rune* runestrdup(Rune*);
+extern Rune* runestrncat(Rune*, Rune*, long);
+extern int runestrncmp(Rune*, Rune*, long);
+extern Rune* runestrrchr(Rune*, Rune);
+extern long runestrlen(Rune*);
+extern Rune* runestrstr(Rune*, Rune*);
+
+extern Rune tolowerrune(Rune);
+extern Rune totitlerune(Rune);
+extern Rune toupperrune(Rune);
+extern int isalpharune(Rune);
+extern int islowerrune(Rune);
+extern int isspacerune(Rune);
+extern int istitlerune(Rune);
+extern int isupperrune(Rune);
+
+/*
+ * malloc
+ */
+extern void* malloc(size_t);
+extern void* mallocz(ulong, int);
+extern void free(void*);
+extern ulong msize(void*);
+extern void* calloc(size_t, size_t);
+extern void* realloc(void*, size_t);
+extern void setmalloctag(void*, ulong);
+extern void setrealloctag(void*, ulong);
+extern ulong getmalloctag(void*);
+extern ulong getrealloctag(void*);
+extern void* malloctopoolblock(void*);
+
+/*
+ * print routines
+ */
+typedef struct Fmt Fmt;
+struct Fmt{
+ uchar runes; /* output buffer is runes or chars? */
+ void *start; /* of buffer */
+ void *to; /* current place in the buffer */
+ void *stop; /* end of the buffer; overwritten if flush fails */
+ int (*flush)(Fmt *); /* called when to == stop */
+ void *farg; /* to make flush a closure */
+ int nfmt; /* num chars formatted so far */
+ va_list args; /* args passed to dofmt */
+ int r; /* % format Rune */
+ int width;
+ int prec;
+ ulong flags;
+};
+
+enum{
+ FmtWidth = 1,
+ FmtLeft = FmtWidth << 1,
+ FmtPrec = FmtLeft << 1,
+ FmtSharp = FmtPrec << 1,
+ FmtSpace = FmtSharp << 1,
+ FmtSign = FmtSpace << 1,
+ FmtZero = FmtSign << 1,
+ FmtUnsigned = FmtZero << 1,
+ FmtShort = FmtUnsigned << 1,
+ FmtLong = FmtShort << 1,
+ FmtVLong = FmtLong << 1,
+ FmtComma = FmtVLong << 1,
+ FmtByte = FmtComma << 1,
+
+ FmtFlag = FmtByte << 1
+};
+
+extern int print(char*, ...);
+extern char* seprint(char*, char*, char*, ...);
+extern char* vseprint(char*, char*, char*, va_list);
+extern int snprint(char*, int, char*, ...);
+extern int vsnprint(char*, int, char*, va_list);
+extern char* smprint(char*, ...);
+extern char* vsmprint(char*, va_list);
+extern int sprint(char*, char*, ...);
+extern int fprint(int, char*, ...);
+extern int vfprint(int, char*, va_list);
+
+extern int runesprint(Rune*, char*, ...);
+extern int runesnprint(Rune*, int, char*, ...);
+extern int runevsnprint(Rune*, int, char*, va_list);
+extern Rune* runeseprint(Rune*, Rune*, char*, ...);
+extern Rune* runevseprint(Rune*, Rune*, char*, va_list);
+extern Rune* runesmprint(char*, ...);
+extern Rune* runevsmprint(char*, va_list);
+
+extern int fmtfdinit(Fmt*, int, char*, int);
+extern int fmtfdflush(Fmt*);
+extern int fmtstrinit(Fmt*);
+extern char* fmtstrflush(Fmt*);
+extern int runefmtstrinit(Fmt*);
+extern Rune* runefmtstrflush(Fmt*);
+
+extern int fmtinstall(int, int (*)(Fmt*));
+extern int dofmt(Fmt*, char*);
+extern int dorfmt(Fmt*, Rune*);
+extern int fmtprint(Fmt*, char*, ...);
+extern int fmtvprint(Fmt*, char*, va_list);
+extern int fmtrune(Fmt*, int);
+extern int fmtstrcpy(Fmt*, char*);
+extern int fmtrunestrcpy(Fmt*, Rune*);
+/*
+ * error string for %r
+ * supplied on per os basis, not part of fmt library
+ */
+extern int errfmt(Fmt *f);
+
+/*
+ * quoted strings
+ */
+extern char *unquotestrdup(char*);
+extern Rune *unquoterunestrdup(Rune*);
+extern char *quotestrdup(char*);
+extern Rune *quoterunestrdup(Rune*);
+extern int quotestrfmt(Fmt*);
+extern int quoterunestrfmt(Fmt*);
+extern void quotefmtinstall(void);
+extern int (*doquote)(int);
+
+/*
+ * random number
+ */
+
+extern int nrand(int);
+extern ulong truerand(void);
+extern ulong ntruerand(ulong);
+
+/*
+ * math
+ */
+extern int isNaN(double);
+extern int isInf(double, int);
+extern double pow(double, double);
+
+/*
+ * Time-of-day
+ */
+
+typedef struct Tm Tm;
+struct Tm {
+ int sec;
+ int min;
+ int hour;
+ int mday;
+ int mon;
+ int year;
+ int wday;
+ int yday;
+ char zone[4];
+ int tzoff;
+};
+extern vlong osnsec(void);
+#define nsec osnsec
+
+/*
+ * one-of-a-kind
+ */
+extern void _assert(char*);
+extern double charstod(int(*)(void*), void*);
+extern char* cleanname(char*);
+extern double frexp(double, int*);
+extern ulong getcallerpc(void*);
+extern int getfields(char*, char**, int, int, char*);
+extern char* getuser(void);
+extern char* getwd(char*, int);
+extern double ipow10(int);
+extern double ldexp(double, int);
+extern double modf(double, double*);
+extern void perror(const char*);
+extern double pow10(int);
+extern uvlong strtoull(const char*, char**, int);
+extern void sysfatal(char*, ...);
+extern int dec64(uchar*, int, char*, int);
+extern int enc64(char*, int, uchar*, int);
+extern int dec32(uchar*, int, char*, int);
+extern int enc32(char*, int, uchar*, int);
+extern int dec16(uchar*, int, char*, int);
+extern int enc16(char*, int, uchar*, int);
+extern int encodefmt(Fmt*);
+
+/*
+ * synchronization
+ */
+typedef
+struct Lock {
+ ulong val;
+ int pid;
+} Lock;
+
+extern ulong _tas(ulong*);
+
+extern void lock(Lock*);
+extern void unlock(Lock*);
+extern int canlock(Lock*);
+
+typedef struct QLock QLock;
+struct QLock
+{
+ Lock use; /* to access Qlock structure */
+ Proc *head; /* next process waiting for object */
+ Proc *tail; /* last process waiting for object */
+ int locked; /* flag */
+};
+
+extern void qlock(QLock*);
+extern void qunlock(QLock*);
+extern int canqlock(QLock*);
+extern void _qlockinit(ulong (*)(ulong, ulong)); /* called only by the thread library */
+
+typedef
+struct RWLock
+{
+ Lock l; /* Lock modify lock */
+ QLock x; /* Mutual exclusion lock */
+ QLock k; /* Lock for waiting writers */
+ int readers; /* Count of readers in lock */
+} RWLock;
+
+extern int canrlock(RWLock*);
+extern int canwlock(RWLock*);
+extern void rlock(RWLock*);
+extern void runlock(RWLock*);
+extern void wlock(RWLock*);
+extern void wunlock(RWLock*);
+
+/*
+ * network dialing
+ */
+#define NETPATHLEN 40
+
+/*
+ * system calls
+ *
+ */
+
+#define STATMAX 65535U /* max length of machine-independent stat structure */
+#define DIRMAX (sizeof(Dir)+STATMAX) /* max length of Dir structure */
+#define ERRMAX 128 /* max length of error string */
+
+#define MORDER 0x0003 /* mask for bits defining order of mounting */
+#define MREPL 0x0000 /* mount replaces object */
+#define MBEFORE 0x0001 /* mount goes before others in union directory */
+#define MAFTER 0x0002 /* mount goes after others in union directory */
+#define MCREATE 0x0004 /* permit creation in mounted directory */
+#define MCACHE 0x0010 /* cache some data */
+#define MMASK 0x0017 /* all bits on */
+
+#define OREAD 0 /* open for read */
+#define OWRITE 1 /* write */
+#define ORDWR 2 /* read and write */
+#define OEXEC 3 /* execute, == read but check execute permission */
+#define OTRUNC 16 /* or'ed in (except for exec), truncate file first */
+#define OCEXEC 32 /* or'ed in, close on exec */
+#define ORCLOSE 64 /* or'ed in, remove on close */
+#define OEXCL 0x1000 /* or'ed in, exclusive use (create only) */
+
+#define AEXIST 0 /* accessible: exists */
+#define AEXEC 1 /* execute access */
+#define AWRITE 2 /* write access */
+#define AREAD 4 /* read access */
+
+/* bits in Qid.type */
+#define QTDIR 0x80 /* type bit for directories */
+#define QTAPPEND 0x40 /* type bit for append only files */
+#define QTEXCL 0x20 /* type bit for exclusive use files */
+#define QTMOUNT 0x10 /* type bit for mounted channel */
+#define QTAUTH 0x08 /* type bit for authentication file */
+#define QTFILE 0x00 /* plain file */
+
+/* bits in Dir.mode */
+#define DMDIR 0x80000000 /* mode bit for directories */
+#define DMAPPEND 0x40000000 /* mode bit for append only files */
+#define DMEXCL 0x20000000 /* mode bit for exclusive use files */
+#define DMMOUNT 0x10000000 /* mode bit for mounted channel */
+#define DMAUTH 0x08000000 /* mode bit for authentication file */
+#define DMREAD 0x4 /* mode bit for read permission */
+#define DMWRITE 0x2 /* mode bit for write permission */
+#define DMEXEC 0x1 /* mode bit for execute permission */
+
+typedef
+struct Qid
+{
+ uvlong path;
+ ulong vers;
+ uchar type;
+} Qid;
+
+typedef
+struct Dir {
+ /* system-modified data */
+ ushort type; /* server type */
+ uint dev; /* server subtype */
+ /* file data */
+ Qid qid; /* unique id from server */
+ ulong mode; /* permissions */
+ ulong atime; /* last read time */
+ ulong mtime; /* last write time */
+ vlong length; /* file length */
+ char *name; /* last element of path */
+ char *uid; /* owner name */
+ char *gid; /* group name */
+ char *muid; /* last modifier name */
+} Dir;
+
+extern Dir* dirstat(char*);
+extern Dir* dirfstat(int);
+extern int dirwstat(char*, Dir*);
+extern int dirfwstat(int, Dir*);
+extern long dirread(int, Dir**);
+extern void nulldir(Dir*);
+extern long dirreadall(int, Dir**);
+
+typedef
+struct Waitmsg
+{
+ int pid; /* of loved one */
+ ulong time[3]; /* of loved one & descendants */
+ char *msg;
+} Waitmsg;
+
+extern void _exits(char*);
+
+extern void exits(char*);
+extern int create(char*, int, int);
+extern int errstr(char*, uint);
+
+extern void perror(const char*);
+extern long readn(int, void*, long);
+extern int remove(const char*);
+extern void rerrstr(char*, uint);
+extern vlong seek(int, vlong, int);
+extern int segflush(void*, ulong);
+extern void werrstr(char*, ...);
+
+extern char *argv0;
+#define ARGBEGIN for((argv0||(argv0=*argv)),argv++,argc--;\
+ argv[0] && argv[0][0]=='-' && argv[0][1];\
+ argc--, argv++) {\
+ char *_args, *_argt;\
+ Rune _argc;\
+ _args = &argv[0][1];\
+ if(_args[0]=='-' && _args[1]==0){\
+ argc--; argv++; break;\
+ }\
+ _argc = 0;\
+ while(*_args && (_args += chartorune(&_argc, _args)))\
+ switch(_argc)
+#define ARGEND SET(_argt);USED(_argt);USED(_argc); USED(_args);}USED(argv); USED(argc);
+#define ARGF() (_argt=_args, _args="",\
+ (*_argt? _argt: argv[1]? (argc--, *++argv): 0))
+#define EARGF(x) (_argt=_args, _args="",\
+ (*_argt? _argt: argv[1]? (argc--, *++argv): ((x), abort(), (char*)0)))
+
+#define ARGC() _argc
+
+/*
+ * Extensions for Inferno to basic libc.h
+ */
+
+#define setbinmode()
+
+/*
+ * Extensions for emu kernel emulation
+ */
+#ifdef EMU
+
+extern Proc *getup(void);
+#define up (getup())
+
+/*
+ * This structure must agree with FPsave and FPrestore asm routines
+ */
+
+// something is at odds between i386/fpu.h and some of the thread headers
+#define fp_control inffp_control
+#define fp_control_t inffp_control_t
+#define fp_status inffp_status
+#define fp_status_t inffp_status_t
+
+#include <architecture/i386/fpu.h>
+
+typedef struct FPU FPU;
+struct FPU
+{
+ fp_state_t env;
+};
+
+#undef fp_control
+#undef fp_control_t
+#undef fp_status
+#undef fp_status_t
+
+typedef sigjmp_buf osjmpbuf;
+#define ossetjmp(buf) sigsetjmp(buf, 1)
+#endif
diff --git a/MacOSX/power/bin/mkext b/MacOSX/power/bin/mkext
new file mode 100644
index 00000000..6a0d475b
--- /dev/null
+++ b/MacOSX/power/bin/mkext
Binary files differ
diff --git a/appl/cmd/ar.b b/appl/cmd/ar.b
new file mode 100644
index 00000000..8ef237b8
--- /dev/null
+++ b/appl/cmd/ar.b
@@ -0,0 +1,856 @@
+implement Ar;
+
+#
+# ar - portable (ascii) format version
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "string.m";
+ str: String;
+
+Ar: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+ARMAG: con "!<arch>\n";
+SARMAG: con len ARMAG;
+ARFMAG0: con byte '`';
+ARFMAG1: con byte '\n';
+SARNAME: con 16; # ancient limit
+
+#
+# printable archive header
+# name[SARNAME] date[12] uid[6] gid[6] mode[8] size[10] fmag[2]
+#
+Oname: con 0;
+Lname: con SARNAME;
+Odate: con Oname+Lname;
+Ldate: con 12;
+Ouid: con Odate+Ldate;
+Luid: con 6;
+Ogid: con Ouid+Luid;
+Lgid: con 6;
+Omode: con Ogid+Lgid;
+Lmode: con 8;
+Osize: con Omode+Lmode;
+Lsize: con 10;
+Ofmag: con Osize+Lsize;
+Lfmag: con 2;
+SAR_HDR: con Ofmag+Lfmag; # 60
+
+#
+# The algorithm uses up to 3 temp files. The "pivot contents" is the
+# archive contents specified by an a, b, or i option. The temp files are
+# astart - contains existing contentss up to and including the pivot contents.
+# amiddle - contains new files moved or inserted behind the pivot.
+# aend - contains the existing contentss that follow the pivot contents.
+# When all contentss have been processed, function 'install' streams the
+# temp files, in order, back into the archive.
+#
+
+Armember: adt { # one per archive contents
+ name: string; # trimmed
+ length: int;
+ date: int;
+ uid: int;
+ gid: int;
+ mode: int;
+ size: int;
+ contents: array of byte;
+ fd: ref Sys->FD; # if contents is nil and fd is not nil, fd has contents
+ next: cyclic ref Armember;
+
+ new: fn(name: string, fd: ref Sys->FD): ref Armember;
+ rdhdr: fn(b: ref Iobuf): ref Armember;
+ read: fn(m: self ref Armember, b: ref Iobuf): int;
+ wrhdr: fn(m: self ref Armember, fd: ref Sys->FD);
+ write: fn(m: self ref Armember, fd: ref Sys->FD);
+ skip: fn(m: self ref Armember, b: ref Iobuf);
+ replace: fn(m: self ref Armember, name: string, fd: ref Sys->FD);
+ copyout: fn(m: self ref Armember, b: ref Iobuf, destfd: ref Sys->FD);
+};
+
+Arfile: adt { # one per tempfile
+ fd: ref Sys->FD; # paging file descriptor, nil if none allocated
+
+ head: ref Armember;
+ tail: ref Armember;
+
+ new: fn(): ref Arfile;
+ copy: fn(ar: self ref Arfile, b: ref Iobuf, mem: ref Armember);
+ insert: fn(ar: self ref Arfile, mem: ref Armember);
+ stream: fn(ar: self ref Arfile, fd: ref Sys->FD);
+ page: fn(ar: self ref Arfile): int;
+};
+
+File: adt {
+ name: string;
+ trimmed: string;
+ found: int;
+};
+
+man := "mrxtdpq";
+opt := "uvnbailo";
+
+aflag := 0;
+bflag := 0;
+cflag := 0;
+oflag := 0;
+uflag := 0;
+vflag := 0;
+
+pivotname: string;
+bout: ref Iobuf;
+stderr: ref Sys->FD;
+parts: array of ref Arfile;
+
+comfun: ref fn(a: string, f: array of ref File);
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ daytime = load Daytime Daytime->PATH;
+ str = load String String->PATH;
+
+ stderr = sys->fildes(2);
+ bout = bufio->fopen(sys->fildes(1), Sys->OWRITE);
+ if(len args < 3)
+ usage();
+ args = tl args;
+ s := hd args; args = tl args;
+ for(i := 0; i < len s; i++){
+ case s[i] {
+ 'a' => aflag = 1;
+ 'b' => bflag = 1;
+ 'c' => cflag = 1;
+ 'd' => setcom(dcmd);
+ 'i' => bflag = 1;
+ 'l' => ; # ignored
+ 'm' => setcom(mcmd);
+ 'o' => oflag = 1;
+ 'p' => setcom(pcmd);
+ 'q' => setcom(qcmd);
+ 'r' => setcom(rcmd);
+ 't' => setcom(tcmd);
+ 'u' => uflag = 1;
+ 'v' => vflag = 1;
+ 'x' => setcom(xcmd);
+ * =>
+ sys->fprint(stderr, "ar: bad option `%c'\n", s[i]);
+ usage();
+ }
+ }
+ if(aflag && bflag){
+ sys->fprint(stderr, "ar: only one of 'a' and 'b' can be specified\n");
+ usage();
+ }
+ if(aflag || bflag){
+ pivotname = trim(hd args); args = tl args;
+ if(len args < 2)
+ usage();
+ }
+ if(comfun == nil){
+ if(uflag == 0){
+ sys->fprint(stderr, "ar: one of [%s] must be specified\n", man);
+ usage();
+ }
+ setcom(rcmd);
+ }
+ cp := hd args; args = tl args;
+ files := array[len args] of ref File;
+ for(i = 0; args != nil; args = tl args)
+ files[i++] = ref File(hd args, trim(hd args), 0);
+ comfun(cp, files); # do the command
+ for(i = 0; i < len files; i++)
+ if(!files[i].found){
+ sys->fprint(stderr, "ar: %s not found\n", files[i].name);
+ cp = "error";
+ }
+ bout.flush();
+ if(cp != nil)
+ raise "fail:"+cp;
+}
+
+#
+# select a command
+#
+setcom(fun: ref fn(s: string, f: array of ref File))
+{
+ if(comfun != nil){
+ sys->fprint(stderr, "ar: only one of [%s] allowed\n", man);
+ usage();
+ }
+ comfun = fun;
+}
+
+#
+# perform the 'r' and 'u' commands
+#
+rcmd(arname: string, files: array of ref File)
+{
+ bar := openar(arname, Sys->ORDWR, 1);
+ parts = array[2] of {Arfile.new(), nil};
+ ap := parts[0];
+ if(bar != nil){
+ while((mem := Armember.rdhdr(bar)) != nil){
+ if(bamatch(mem.name, pivotname)) # check for pivot
+ ap = parts[1] = Arfile.new();
+ f := match(files, mem.name);
+ if(f == nil){
+ ap.copy(bar, mem);
+ continue;
+ }
+ f.found = 1;
+ dfd := sys->open(f.name, Sys->OREAD);
+ if(dfd == nil){
+ if(len files > 0)
+ sys->fprint(stderr, "ar: cannot open %s: %r\n", f.name);
+ ap.copy(bar, mem);
+ continue;
+ }
+ if(uflag){
+ (ok, d) := sys->fstat(dfd);
+ if(ok < 0 || d.mtime <= mem.date){
+ if(ok < 0)
+ sys->fprint(stderr, "ar: cannot stat %s: %r\n", f.name);
+ ap.copy(bar, mem);
+ continue;
+ }
+ }
+ mem.skip(bar);
+ mesg('r', f.name);
+ mem.replace(f.name, dfd);
+ ap.insert(mem);
+ dfd = nil;
+ }
+ }
+ # copy in remaining files named on command line
+ for(i := 0; i < len files; i++){
+ f := files[i];
+ if(f.found)
+ continue;
+ f.found = 1;
+ dfd := sys->open(f.name, Sys->OREAD);
+ if(dfd != nil){
+ mesg('a', f.name);
+ parts[0].insert(Armember.new(f.trimmed, dfd));
+ }else
+ sys->fprint(stderr, "ar: cannot open %s: %r\n", f.name);
+ }
+ if(bar == nil && !cflag)
+ install(arname, parts, 1); # issue 'creating' msg
+ else
+ install(arname, parts, 0);
+}
+
+dcmd(arname: string, files: array of ref File)
+{
+ if(len files == 0)
+ return;
+ changed := 0;
+ parts = array[] of {Arfile.new()};
+ bar := openar(arname, Sys->ORDWR, 0);
+ while((mem := Armember.rdhdr(bar)) != nil){
+ if(match(files, mem.name) != nil){
+ mesg('d', mem.name);
+ mem.skip(bar);
+ changed = 1;
+ }else
+ parts[0].copy(bar, mem);
+ mem = nil; # conserves memory
+ }
+ if(changed)
+ install(arname, parts, 0);
+}
+
+xcmd(arname: string, files: array of ref File)
+{
+ bar := openar(arname, Sys->OREAD, 0);
+ i := 0;
+ while((mem := Armember.rdhdr(bar)) != nil){
+ if((f := match(files, mem.name)) != nil){
+ f.found = 1;
+ fd := sys->create(f.name, Sys->OWRITE, mem.mode & 8r777);
+ if(fd == nil){
+ sys->fprint(stderr, "ar: cannot create %s: %r\n", f.name);
+ mem.skip(bar);
+ }else{
+ mesg('x', f.name);
+ mem.copyout(bar, fd);
+ if(oflag){
+ dx := sys->nulldir;
+ dx.atime = mem.date;
+ dx.mtime = mem.date;
+ if(sys->fwstat(fd, dx) < 0)
+ sys->fprint(stderr, "ar: can't set times on %s: %r", f.name);
+ }
+ fd = nil;
+ mem = nil;
+ }
+ if(len files > 0 && ++i >= len files)
+ break;
+ }else
+ mem.skip(bar);
+ }
+}
+
+pcmd(arname: string, files: array of ref File)
+{
+ bar := openar(arname, Sys->OREAD, 0);
+ i := 0;
+ while((mem := Armember.rdhdr(bar)) != nil){
+ if((f := match(files, mem.name)) != nil){
+ if(vflag)
+ sys->print("\n<%s>\n\n", f.name);
+ mem.copyout(bar, sys->fildes(1));
+ if(len files > 0 && ++i >= len files)
+ break;
+ }else
+ mem.skip(bar);
+ mem = nil; # we no longer need the contents
+ }
+}
+
+mcmd(arname: string, files: array of ref File)
+{
+ if(len files == 0)
+ return;
+ parts = array[3] of {Arfile.new(), Arfile.new(), nil};
+ bar := openar(arname, Sys->ORDWR, 0);
+ ap := parts[0];
+ while((mem := Armember.rdhdr(bar)) != nil){
+ if(bamatch(mem.name, pivotname))
+ ap = parts[2] = Arfile.new();
+ if((f := match(files, mem.name)) != nil){
+ mesg('m', f.name);
+ parts[1].copy(bar, mem);
+ }else
+ ap.copy(bar, mem);
+ }
+ if(pivotname != nil && parts[2] == nil)
+ sys->fprint(stderr, "ar: %s not found - files moved to end\n", pivotname);
+ install(arname, parts, 0);
+}
+
+tcmd(arname: string, files: array of ref File)
+{
+ bar := openar(arname, Sys->OREAD, 0);
+ while((mem := Armember.rdhdr(bar)) != nil){
+ if((f := match(files, mem.name)) != nil){
+ longls := "";
+ if(vflag)
+ longls = longtext(mem)+" ";
+ bout.puts(longls+f.trimmed+"\n");
+ }
+ mem.skip(bar);
+ mem = nil;
+ }
+}
+
+qcmd(arname: string, files: array of ref File)
+{
+ if(aflag || bflag){
+ sys->fprint(stderr, "ar: abi not allowed with q\n");
+ raise "fail:usage";
+ }
+ fd := openrawar(arname, Sys->ORDWR, 1);
+ if(fd == nil){
+ if(!cflag)
+ sys->fprint(stderr, "ar: creating %s\n", arname);
+ fd = arcreate(arname);
+ }
+ # leave note group behind when writing archive; i.e. sidestep interrupts
+ sys->seek(fd, big 0, 2); # append
+ for(i := 0; i < len files; i++){
+ f := files[i];
+ f.found = 1;
+ dfd := sys->open(f.name, Sys->OREAD);
+ if(dfd != nil){
+ mesg('q', f.name);
+ mem := Armember.new(f.trimmed, dfd);
+ if(mem != nil){
+ mem.write(fd);
+ mem = nil;
+ }
+ }else
+ sys->fprint(stderr, "ar: cannot open %s: %r\n", f.name);
+ }
+}
+
+#
+# open an archive and validate its header
+#
+openrawar(arname: string, mode: int, errok: int): ref Sys->FD
+{
+ fd := sys->open(arname, mode);
+ if(fd == nil){
+ if(!errok){
+ sys->fprint(stderr, "ar: cannot open %s: %r\n", arname);
+ raise "fail:error";
+ }
+ return nil;
+ }
+ mbuf := array[SARMAG] of byte;
+ if(sys->read(fd, mbuf, SARMAG) != SARMAG || string mbuf != ARMAG){
+ sys->fprint(stderr, "ar: %s not in archive format\n", arname);
+ raise "fail:error";
+ }
+ return fd;
+}
+
+openar(arname: string, mode: int, errok: int): ref Iobuf
+{
+ fd := openrawar(arname, mode, errok);
+ if(fd == nil)
+ return nil;
+ bfd := bufio->fopen(fd, mode);
+ bfd.seek(big SARMAG, 0);
+ return bfd;
+}
+
+#
+# create an archive and set its header
+#
+arcreate(arname: string): ref Sys->FD
+{
+ fd := sys->create(arname, Sys->OWRITE, 8r666);
+ if(fd == nil){
+ sys->fprint(stderr, "ar: cannot create %s: %r\n", arname);
+ raise "fail:create";
+ }
+ a := array of byte ARMAG;
+ mustwrite(fd, a, len a);
+ return fd;
+}
+
+#
+# error handling
+#
+wrerr()
+{
+ sys->fprint(stderr, "ar: write error: %r\n");
+ raise "fail:write error";
+}
+
+rderr()
+{
+ sys->fprint(stderr, "ar: read error: %r\n");
+ raise "fail:read error";
+}
+
+phaseerr(offset: big)
+{
+ sys->fprint(stderr, "ar: phase error at offset %bd\n", offset);
+ raise "fail:phase error";
+}
+
+usage()
+{
+ sys->fprint(stderr, "usage: ar [%s][%s] archive files ...\n", opt, man);
+ raise "fail:usage";
+}
+
+#
+# concatenate the several sequences of members into one archive
+#
+install(arname: string, seqs: array of ref Arfile, createflag: int)
+{
+ # leave process group behind when copying back; i.e. sidestep interrupts
+ sys->pctl(Sys->NEWPGRP, nil);
+
+ if(createflag)
+ sys->fprint(stderr, "ar: creating %s\n", arname);
+ fd := arcreate(arname);
+ for(i := 0; i < len seqs; i++)
+ if((ap := seqs[i]) != nil)
+ ap.stream(fd);
+}
+
+#
+# return the command line File matching a given name
+#
+match(files: array of ref File, file: string): ref File
+{
+ if(len files == 0)
+ return ref File(file, file, 0); # empty list always matches
+ for(i := 0; i < len files; i++)
+ if(!files[i].found && files[i].trimmed == file){
+ files[i].found = 1;
+ return files[i];
+ }
+ return nil;
+}
+
+#
+# is `file' the pivot member's name and is the archive positioned
+# at the correct point wrt after or before options? return true if so.
+#
+state := 0;
+
+bamatch(file: string, pivot: string): int
+{
+ case state {
+ 0 => # looking for position file
+ if(aflag){
+ if(file == pivot)
+ state = 1;
+ }else if(bflag){
+ if(file == pivot){
+ state = 2; # found
+ return 1;
+ }
+ }
+ 1 => # found - after previous file
+ state = 2;
+ return 1;
+ 2 => # already found position file
+ ;
+ }
+ return 0;
+}
+
+#
+# output a message, if 'v' option was specified
+#
+mesg(c: int, file: string)
+{
+ if(vflag)
+ bout.puts(sys->sprint("%c - %s\n", c, file));
+}
+
+#
+# return just the file name
+#
+trim(s: string): string
+{
+ for(j := len s; j > 0 && s[j-1] == '/';)
+ j--;
+ k := 0;
+ for(i := 0; i < j; i++)
+ if(s[i] == '/')
+ k = i+1;
+ return s[k: j];
+}
+
+longtext(mem: ref Armember): string
+{
+ s := modes(mem.mode);
+ s += sys->sprint(" %3d/%1d", mem.uid, mem.gid);
+ s += sys->sprint(" %7ud", mem.size);
+ t := daytime->text(daytime->local(mem.date));
+ return s+sys->sprint(" %-12.12s %-4.4s ", t[4:], t[24:]);
+}
+
+mtab := array[] of {
+ "---", "--x", "-w-", "-wx",
+ "r--", "r-x", "rw-", "rwx"
+};
+
+modes(mode: int): string
+{
+ return mtab[(mode>>6)&7]+mtab[(mode>>3)&7]+mtab[mode&7];
+}
+
+#
+# read the header for the next archive contents
+#
+Armember.rdhdr(b: ref Iobuf): ref Armember
+{
+ buf := array[SAR_HDR] of byte;
+ if((n := b.read(buf, len buf)) != len buf){
+ if(n == 0)
+ return nil;
+ if(n > 0)
+ sys->werrstr("unexpected end-of-file");
+ rderr();
+ }
+ mem := ref Armember;
+ for(i := Oname+Lname; i > Oname; i--)
+ if(buf[i-1] != byte '/' && buf[i-1] != byte ' ')
+ break;
+ mem.name = string buf[Oname:i];
+ mem.date = intof(buf[Odate: Odate+Ldate], 10);
+ mem.uid = intof(buf[Ouid: Ouid+Luid], 10);
+ mem.gid = intof(buf[Ogid: Ogid+Lgid], 10);
+ mem.mode = intof(buf[Omode: Omode+Lmode], 8);
+ mem.size = intof(buf[Osize: Osize+Lsize], 10);
+ if(buf[Ofmag] != ARFMAG0 || buf[Ofmag+1] != ARFMAG1)
+ phaseerr(b.offset()-big SAR_HDR);
+ return mem;
+}
+
+intof(a: array of byte, base: int): int
+{
+ for(i := len a; i > 0; i--)
+ if(a[i-1] != byte ' '){
+ a = a[0:i];
+ break;
+ }
+ (n, s) := str->toint(string a, base);
+ if(s != nil){
+ sys->fprint(stderr, "ar: invalid integer in archive member's header: %q\n", string a);
+ raise "fail:error";
+ }
+ return n;
+}
+
+Armember.wrhdr(mem: self ref Armember, fd: ref Sys->FD)
+{
+ b := array[SAR_HDR] of {* => byte ' '};
+ nm := array of byte mem.name;
+ if(len nm > Lname)
+ nm = nm[0:Lname];
+ b[Oname:] = nm;
+ b[Odate:] = sys->aprint("%-12ud", mem.date);
+ b[Ouid:] = sys->aprint("%-6d", 0);
+ b[Ogid:] = sys->aprint("%-6d", 0);
+ b[Omode:] = sys->aprint("%-8uo", mem.mode);
+ b[Osize:] = sys->aprint("%-10ud", mem.size);
+ b[Ofmag] = ARFMAG0;
+ b[Ofmag+1] = ARFMAG1;
+ mustwrite(fd, b, len b);
+}
+
+#
+# make a new member from the given file, with the file's contents
+#
+Armember.new(name: string, fd: ref Sys->FD): ref Armember
+{
+ mem := ref Armember;
+ mem.replace(name, fd);
+ return mem;
+}
+
+#
+# replace the contents of an existing member
+#
+Armember.replace(mem: self ref Armember, name: string, fd: ref Sys->FD)
+{
+ (ok, d) := sys->fstat(fd);
+ if(ok < 0){
+ sys->fprint(stderr, "ar: cannot stat %s: %r\n", name);
+ raise "fail:no stat";
+ }
+ mem.name = trim(name);
+ mem.date = d.mtime;
+ mem.uid = 0;
+ mem.gid = 0;
+ mem.mode = d.mode & 8r777;
+ mem.size = int d.length;
+ if(big mem.size != d.length){
+ sys->fprint(stderr, "ar: file %s too big\n", name);
+ raise "fail:error";
+ }
+ mem.fd = fd;
+ mem.contents = nil; # will be copied across from fd when needed
+}
+
+#
+# read the contents of an archive member
+#
+Armember.read(mem: self ref Armember, b: ref Iobuf): int
+{
+ if(mem.contents != nil)
+ return len mem.contents;
+ mem.contents = buffer(mem.size + (mem.size&1));
+ n := b.read(mem.contents, len mem.contents);
+ if(n != len mem.contents){
+ if(n >= 0)
+ sys->werrstr("unexpected end-of-file");
+ rderr();
+ }
+ return n;
+}
+
+mustwrite(fd: ref Sys->FD, buf: array of byte, n: int)
+{
+ if(sys->write(fd, buf, n) != n)
+ wrerr();
+}
+
+#
+# write an archive member to ofd, including header
+#
+Armember.write(mem: self ref Armember, ofd: ref Sys->FD)
+{
+ mem.wrhdr(ofd);
+ if(mem.contents != nil){
+ mustwrite(ofd, mem.contents, len mem.contents);
+ return;
+ }
+ if(mem.fd == nil)
+ raise "ar: write nil fd";
+ buf := array[Sys->ATOMICIO] of byte; # could be bigger
+ for(nr := mem.size; nr > 0;){
+ n := nr;
+ if(n > len buf)
+ n = len buf;
+ n = sys->read(mem.fd, buf, n);
+ if(n <= 0){
+ if(n == 0)
+ sys->werrstr("unexpected end-of-file");
+ rderr();
+ }
+ mustwrite(ofd, buf, n);
+ nr -= n;
+ }
+ if(mem.size & 1)
+ mustwrite(ofd, array[] of {byte '\n'}, 1);
+}
+
+#
+# seek past the current member's contents in b
+#
+Armember.skip(mem: self ref Armember, b: ref Iobuf)
+{
+ b.seek(big(mem.size + (mem.size&1)), 1);
+}
+
+#
+# copy a member's contents from memory or directly from an archive to another file
+#
+Armember.copyout(mem: self ref Armember, b: ref Iobuf, ofd: ref Sys->FD)
+{
+ if(mem.contents != nil){
+ mustwrite(ofd, mem.contents, len mem.contents);
+ return;
+ }
+ buf := array[Sys->ATOMICIO] of byte; # could be bigger
+ for(nr := mem.size; nr > 0;){
+ n := nr;
+ if(n > len buf)
+ n = len buf;
+ n = b.read(buf, n);
+ if(n <= 0){
+ if(n == 0)
+ sys->werrstr("unexpected end-of-file");
+ rderr();
+ }
+ mustwrite(ofd, buf, n);
+ nr -= n;
+ }
+ if(mem.size & 1)
+ b.getc();
+}
+
+#
+# Temp file I/O subsystem. We attempt to cache all three temp files in
+# core. When we run out of memory we spill to disk.
+# The I/O model assumes that temp files:
+# 1) are only written on the end
+# 2) are only read from the beginning
+# 3) are only read after all writing is complete.
+# The architecture uses one control block per temp file. Each control
+# block anchors a chain of buffers, each containing an archive contents.
+#
+Arfile.new(): ref Arfile
+{
+ return ref Arfile;
+}
+
+#
+# copy the contents of mem at b into the temporary
+#
+Arfile.copy(ap: self ref Arfile, b: ref Iobuf, mem: ref Armember)
+{
+ mem.read(b);
+ ap.insert(mem);
+}
+
+#
+# insert a contents buffer into the contents chain
+#
+Arfile.insert(ap: self ref Arfile, mem: ref Armember)
+{
+ mem.next = nil;
+ if(ap.head == nil)
+ ap.head = mem;
+ else
+ ap.tail.next = mem;
+ ap.tail = mem;
+}
+
+#
+# stream the contents in a temp file to the file referenced by 'fd'.
+#
+Arfile.stream(ap: self ref Arfile, fd: ref Sys->FD)
+{
+ if(ap.fd != nil){ # copy prefix from disk
+ buf := array[Sys->ATOMICIO] of byte;
+ sys->seek(ap.fd, big 0, 0);
+ while((n := sys->read(ap.fd, buf, len buf)) > 0)
+ mustwrite(fd, buf, n);
+ if(n < 0)
+ rderr();
+ ap.fd = nil;
+ }
+ # dump the in-core buffers, which always follow the contents in the temp file
+ for(mem := ap.head; mem != nil; mem = mem.next)
+ mem.write(fd);
+}
+
+#
+# spill a member's contents to disk
+#
+
+totalmem := 0;
+warned := 0;
+tn := 0;
+
+Arfile.page(ap: self ref Arfile): int
+{
+ mem := ap.head;
+ if(ap.fd == nil && !warned){
+ pid := sys->pctl(0, nil);
+ for(i := 0;; i++){
+ name := sys->sprint("/tmp/art%d.%d.%d", pid, tn, i);
+ ap.fd = sys->create(name, Sys->OEXCL | Sys->ORDWR | Sys->ORCLOSE, 8r600);
+ if(ap.fd != nil)
+ break;
+ if(i >= 20){
+ warned =1;
+ sys->fprint(stderr,"ar: warning: can't create temp file %s: %r\n", name);
+ return 0; # we'll simply use the memory
+ }
+ }
+ tn++;
+ }
+ mem.write(ap.fd);
+ ap.head = mem.next;
+ if(ap.tail == mem)
+ ap.tail = mem.next;
+ totalmem -= len mem.contents;
+ return 1;
+}
+
+#
+# account for the space taken by a contents's contents,
+# pushing earlier contentss to disk to keep the space below a
+# reasonable level
+#
+
+buffer(n: int): array of byte
+{
+Flush:
+ while(totalmem + n > 1024*1024){
+ for(i := 0; i < len parts; i++)
+ if(parts[i] != nil && parts[i].page())
+ continue Flush;
+ break;
+ }
+ totalmem += n;
+ return array[n] of byte;
+}
diff --git a/appl/cmd/cddb.b b/appl/cmd/cddb.b
new file mode 100644
index 00000000..6265ba24
--- /dev/null
+++ b/appl/cmd/cddb.b
@@ -0,0 +1,257 @@
+implement Cddb;
+
+# this is a near transliteration of Plan 9 source, and subject to the Lucent Public License 1.02
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "string.m";
+ str: String;
+
+include "arg.m";
+
+Cddb: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+server := "freedb.freedb.org";
+debug := 0;
+tflag := 0;
+Tflag := 0;
+
+Track: adt {
+ n: int;
+ title: string;
+};
+
+Toc: adt {
+ diskid: int;
+ ntrack: int;
+ title: string;
+ track: array of Track;
+};
+
+DPRINT(fd: int, s: string)
+{
+ if(debug)
+ sys->fprint(sys->fildes(fd), "%s", s);
+}
+
+dumpcddb(t: ref Toc)
+{
+ sys->print("title %s\n", t.title);
+ for(i:=0; i<t.ntrack; i++){
+ if(tflag){
+ n := t.track[i+1].n;
+ if(i == t.ntrack-1)
+ n *= 75;
+ s := (n - t.track[i].n)/75;
+ sys->print("%d\t%s\t%d:%2.2d\n", i+1, t.track[i].title, s/60, s%60);
+ }
+ else
+ sys->print("%d\t%s\n", i+1, t.track[i].title);
+ }
+ if(Tflag){
+ s := t.track[i].n;
+ sys->print("Total time: %d:%2.2d\n", s/60, s%60);
+ }
+}
+
+cddbfilltoc(t: ref Toc): int
+{
+ (ok, conn) := sys->dial(netmkaddr(server, "tcp", "888"), nil);
+ if(ok < 0) {
+ sys->fprint(sys->fildes(2), "cddb: cannot dial %s: %r\n", server);
+ return -1;
+ }
+ bin := bufio->fopen(conn.dfd, Bufio->OREAD);
+
+ if((p:=getline(bin)) == nil || atoi(p)/100 != 2)
+ return died(p);
+
+ sys->fprint(conn.dfd, "cddb hello gre plan9 9cd 1.0\r\n");
+ if((p = getline(bin)) == nil || atoi(p)/100 != 2)
+ return died(p);
+
+ #
+ # Protocol level 6 is the same as level 5 except that
+ # the character set is now UTF-8 instead of ISO-8859-1.
+ #
+ sys->fprint(conn.dfd, "proto 6\r\n");
+ if((p = getline(bin)) == nil || atoi(p)/100 != 2)
+ return died(p);
+ DPRINT(2, sys->sprint("%s\n", p));
+
+ sys->fprint(conn.dfd, "cddb query %8.8ux %d", t.diskid, t.ntrack);
+ DPRINT(2, sys->sprint("cddb query %8.8ux %d", t.diskid, t.ntrack));
+ for(i:=0; i<t.ntrack; i++) {
+ sys->fprint(conn.dfd, " %d", t.track[i].n);
+ DPRINT(2, sys->sprint(" %d", t.track[i].n));
+ }
+ sys->fprint(conn.dfd, " %d\r\n", t.track[t.ntrack].n);
+ DPRINT(2, sys->sprint(" %d\r\n", t.track[t.ntrack].n));
+
+ if((p = getline(bin)) == nil || atoi(p)/100 != 2)
+ return died(p);
+ DPRINT(2, sys->sprint("cddb: %s\n", p));
+ (nf, fl) := sys->tokenize(p, " \t\n\r");
+ if(nf < 1)
+ return died(p);
+
+ categ, id: string;
+ case atoi(hd fl) {
+ 200 => # exact match
+ if(nf < 3)
+ return died(p);
+ categ = hd tl fl;
+ id = hd tl tl fl;
+ 211 => # close matches
+ if((p = getline(bin)) == nil)
+ return died(nil);
+ if(p[0] == '.') # no close matches?
+ return died(nil);
+
+ # accept first match
+ (nsf, f) := sys->tokenize(p, " \t\n\r");
+ if(nsf < 2)
+ return died(p);
+ categ = hd f;
+ id = hd tl f;
+
+ # snarf rest of buffer
+ while(p[0] != '.') {
+ if((p = getline(bin)) == nil)
+ return died(p);
+ DPRINT(2, sys->sprint("cddb: %s\n", p));
+ }
+ 202 or # no match
+ * =>
+ return died(p);
+ }
+
+ t.title = "";
+ for(i=0; i<t.ntrack; i++)
+ t.track[i].title = "";
+
+ # fetch results for this cd
+ sys->fprint(conn.dfd, "cddb read %s %s\r\n", categ, id);
+ do {
+ if((p = getline(bin)) == nil)
+ return died(nil);
+DPRINT(2, sys->sprint("cddb %s\n", p));
+ if(len p >= 7 && p[0:7] == "DTITLE=")
+ t.title += p[7:];
+ else if(len p >= 6 && p[0:6] == "TTITLE"&& isdigit(p[6])) {
+ i = atoi(p[6:]);
+ if(i < t.ntrack) {
+ p = p[6:];
+ while(p != nil && isdigit(p[0]))
+ p = p[1:];
+ if(p != nil && p[0] == '=')
+ p = p[1:];
+ t.track[i].title += p;
+ }
+ }
+ } while(p[0] != '.');
+
+ sys->fprint(conn.dfd, "quit\r\n");
+
+ return 0;
+}
+
+getline(f: ref Iobuf): string
+{
+ p := f.gets('\n');
+ while(p != nil && isspace(p[len p-1]))
+ p = p[0: len p-1];
+ return p;
+}
+
+isdigit(c: int): int
+{
+ return c>='0' && c <= '9';
+}
+
+isspace(c: int): int
+{
+ return c == ' ' || c == '\t' || c == '\n' || c == '\r';
+}
+
+died(p: string): int
+{
+ sys->fprint(sys->fildes(2), "cddb: error talking to server\n");
+ if(p != nil){
+ p = p[0:len p-1];
+ sys->fprint(sys->fildes(2), "cddb: server says: %s\n", p);
+ }
+ return -1;
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ str = load String String->PATH;
+
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("cddb [-DTt] [-s server] query diskid n ...");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'D' => debug = 1;
+ 's' => server = arg->earg();
+ 'T' => Tflag = 1; tflag = 1;
+ 't' => tflag = 1;
+ * => arg->usage();
+ }
+ args = arg->argv();
+ argc := len args;
+ if(argc < 3 || hd args != "query")
+ arg->usage();
+ arg = nil;
+
+ ntrack := atoi(hd tl tl args);
+ toc := ref Toc(str->toint(hd tl args, 16).t0, ntrack, nil, array[ntrack+1] of Track);
+ if(argc != 3+toc.ntrack+1){
+ sys->fprint(sys->fildes(2), "cddb: argument count does not match given ntrack");
+ raise "fail:error";
+ }
+ args = tl tl tl args;
+
+ for(i:=0; i<=toc.ntrack; i++){ # <=?
+ toc.track[i].n = atoi(hd args);
+ args = tl args;
+ }
+
+ if(cddbfilltoc(toc) < 0)
+ raise "fail:whoops";
+
+ dumpcddb(toc);
+}
+
+netmkaddr(addr, net, svc: string): string
+{
+ if(net == nil)
+ net = "net";
+ (n, nil) := sys->tokenize(addr, "!");
+ if(n <= 1){
+ if(svc== nil)
+ return sys->sprint("%s!%s", net, addr);
+ return sys->sprint("%s!%s!%s", net, addr, svc);
+ }
+ if(svc == nil || n > 2)
+ return addr;
+ return sys->sprint("%s!%s", addr, svc);
+}
+
+atoi(s: string): int
+{
+ return int s;
+}
diff --git a/appl/cmd/lookman.b b/appl/cmd/lookman.b
deleted file mode 100644
index 53557c8b..00000000
--- a/appl/cmd/lookman.b
+++ /dev/null
@@ -1,250 +0,0 @@
-implement Lookman;
-include "sys.m";
-include "bufio.m";
-include "draw.m";
-
-
-Lookman : module {
- init : fn (ctxt : ref Draw->Context, argv : list of string);
-};
-
-sys : Sys;
-bufio : Bufio;
-Iobuf : import bufio;
-
-ctype := array [256] of { * => byte 0 };
-
-MANINDEX : con "/man/index";
-
-init(nil : ref Draw->Context, argv : list of string)
-{
- sys = load Sys Sys->PATH;
- bufio = load Bufio Bufio->PATH;
-
- if (bufio == nil)
- raise "init:fail";
-
- # setup our char conversion table
- # map upper-case to lower-case
- for (i := 'A'; i <= 'Z'; i++)
- ctype[i] = byte ((i - 'A') + 'a');
-
- # only allow the following chars
- okchars := "abcdefghijklmnopqrstuvwxyz0123456789+.:½ ";
- for (i = 0; i < len okchars; i++) {
- ch := okchars[i];
- ctype[ch] = byte ch;
- }
-
- stdout := bufio->fopen(sys->fildes(1), Sys->OWRITE);
-
- argv = tl argv;
- paths := lookup(argv);
- for (; paths != nil; paths = tl paths)
- stdout.puts(sys->sprint("%s\n", hd paths));
- stdout.flush();
-}
-
-lookup(words : list of string) : list of string
-{
- # open the index file
- manindex := bufio->open(MANINDEX, Sys->OREAD);
- if (manindex == nil) {
- sys->print("cannot open %s: %r\n", MANINDEX);
- return nil;
- }
-
- # convert to lower-case and discard funny chars
- keywords : list of string;
- for (; words != nil; words = tl words) {
- word := hd words;
- kw := "";
- for (i := 0; i < len word; i++) {
- ch := word[i];
- if (ch < len ctype && ctype[ch] != byte 0)
- kw[len kw] = int ctype[ch];
- }
- if (kw != "")
- keywords = kw :: keywords;
- }
-
- if (keywords == nil)
- return nil;
-
- keywords = sortuniq(keywords);
- matches : list of list of string;
-
- for (; keywords != nil; keywords = tl keywords) {
- kw := hd keywords;
- matchlist := look(manindex, '\t', kw);
- pathlist : list of string = nil;
- for (; matchlist != nil; matchlist = tl matchlist) {
- line := hd matchlist;
- (n, toks) := sys->tokenize(line, "\t");
- if (n != 2)
- continue;
- pathlist = hd tl toks :: pathlist;
- }
- if (pathlist != nil)
- matches = pathlist :: matches;
- }
-
- return intersect(matches);
-}
-
-getentry(iob : ref Iobuf) : (string, string)
-{
- while ((s := iob.gets('\n')) != nil) {
- if (s[len s -1] == '\n')
- s = s[0:len s -1];
- if (s == nil)
- continue;
- (n, toks) := sys->tokenize(s, "\t");
- if (n != 2)
- continue;
- return (hd toks, hd tl toks);
- }
- return (nil, nil);
-}
-
-sortuniq(strlist : list of string) : list of string
-{
- strs := array [len strlist] of string;
- for (i := 0; strlist != nil; (i, strlist) = (i+1, tl strlist))
- strs[i] = hd strlist;
-
- # simple sort (greatest first)
- for (i = 0; i < len strs - 1; i++) {
- for (j := i+1; j < len strs; j++)
- if (strs[i] < strs[j])
- (strs[i], strs[j]) = (strs[j], strs[i]);
- }
-
- # construct list (result is ascending)
- r : list of string;
- prev := "";
- for (i = 0; i < len strs; i++) {
- if (strs[i] != prev) {
- r = strs[i] :: r;
- prev = strs[i];
- }
- }
- return r;
-}
-
-intersect(strlists : list of list of string) : list of string
-{
- if (strlists == nil)
- return nil;
-
- okl := hd strlists;
- for (strlists = tl strlists; okl != nil && strlists != nil; strlists = tl strlists) {
- find := hd strlists;
- found : list of string = nil;
- for (; okl != nil; okl = tl okl) {
- ok := hd okl;
- for (scanl := find; scanl != nil; scanl = tl scanl) {
- scan := hd scanl;
- if (scan == ok) {
- found = ok :: found;
- break;
- }
- }
- }
- okl = found;
- }
- return sortuniq(okl);
-}
-
-# binary search for key in f.
-# based on Plan 9 look.c
-#
-look(f: ref Iobuf, sep: int, key: string): list of string
-{
- bot := mid := 0;
- top := int f.seek(big 0, Sys->SEEKEND);
- key = canon(key, sep);
-
- for (;;) {
- mid = (top + bot) / 2;
- f.seek(big mid, Sys->SEEKSTART);
- c: int;
- do {
- c = f.getb();
- mid++;
- } while (c != Bufio->EOF && c != Bufio->ERROR && c != '\n');
- (entry, eof) := getword(f);
- if (entry == nil && eof)
- break;
- entry = canon(entry, sep);
- case comparewords(key, entry) {
- -2 or -1 or 0 =>
- if (top <= mid)
- break;
- top = mid;
- continue;
- 1 or 2 =>
- bot = mid;
- continue;
- }
- break;
- }
- matchlist : list of string;
- f.seek(big bot, Sys->SEEKSTART);
- for (;;) {
- (entry, eof) := getword(f);
- if (entry == nil && eof)
- return matchlist;
- word := canon(entry, sep);
- case comparewords(key, word) {
- -1 or 0 =>
- matchlist = entry :: matchlist;
- continue;
- 1 or 2 =>
- continue;
- }
- break;
- }
- return matchlist;
-}
-
-comparewords(s, t: string): int
-{
- if (s == t)
- return 0;
- i := 0;
- for (; i < len s && i < len t && s[i] == t[i]; i++)
- ;
- if (i >= len s)
- return -1;
- if (i >= len t)
- return 1;
- if (s[i] < t[i])
- return -2;
- return 2;
-}
-
-getword(f: ref Iobuf): (string, int)
-{
- ret := "";
- for (;;) {
- c := f.getc();
- if (c == Bufio->EOF || c == Bufio->ERROR)
- return (ret, 0);
- if (c == '\n')
- break;
- ret[len ret] = c;
- }
- return (ret, 1);
-}
-
-canon(s: string, sep: int): string
-{
- if (sep < 0)
- return s;
- i := 0;
- for (; i < len s; i++)
- if (s[i] == sep)
- break;
- return s[0:i];
-}
diff --git a/appl/cmd/man2html.b b/appl/cmd/man2html.b
new file mode 100644
index 00000000..9eda5940
--- /dev/null
+++ b/appl/cmd/man2html.b
@@ -0,0 +1,1328 @@
+implement Man2html;
+
+include "sys.m";
+ stderr: ref Sys->FD;
+ sys: Sys;
+ print, fprint, sprint: import sys;
+
+
+include "bufio.m";
+
+include "draw.m";
+
+include "daytime.m";
+ dt: Daytime;
+
+include "string.m";
+ str: String;
+
+Man2html: module
+{
+ init: fn(ctxt: ref Draw->Context, args: list of string);
+};
+
+Runeself: con 16r80;
+false, true: con iota;
+
+Troffspec: adt {
+ name: string;
+ value: string;
+};
+
+tspec := array [] of { Troffspec
+ ("ff", "ff"),
+ ("fi", "fi"),
+ ("fl", "fl"),
+ ("Fi", "ffi"),
+ ("ru", "_"),
+ ("em", "&#173;"),
+ ("14", "&#188;"),
+ ("12", "&#189;"),
+ ("co", "&#169;"),
+ ("de", "&#176;"),
+ ("dg", "&#161;"),
+ ("fm", "&#180;"),
+ ("rg", "&#174;"),
+# ("bu", "*"),
+ ("bu", "•"),
+ ("sq", "&#164;"),
+ ("hy", "-"),
+ ("pl", "+"),
+ ("mi", "-"),
+ ("mu", "&#215;"),
+ ("di", "&#247;"),
+ ("eq", "="),
+ ("==", "=="),
+ (">=", ">="),
+ ("<=", "<="),
+ ("!=", "!="),
+ ("+-", "&#177;"),
+ ("no", "&#172;"),
+ ("sl", "/"),
+ ("ap", "&"),
+ ("~=", "~="),
+ ("pt", "oc"),
+ ("gr", "GRAD"),
+ ("->", "->"),
+ ("<-", "<-"),
+ ("ua", "^"),
+ ("da", "v"),
+ ("is", "Integral"),
+ ("pd", "DIV"),
+ ("if", "oo"),
+ ("sr", "-/"),
+ ("sb", "(~"),
+ ("sp", "~)"),
+ ("cu", "U"),
+ ("ca", "(^)"),
+ ("ib", "(="),
+ ("ip", "=)"),
+ ("mo", "C"),
+ ("es", "&Oslash;"),
+ ("aa", "&#180;"),
+ ("ga", "`"),
+ ("ci", "O"),
+ ("L1", "Lucent"),
+ ("sc", "&#167;"),
+ ("dd", "++"),
+ ("lh", "<="),
+ ("rh", "=>"),
+ ("lt", "("),
+ ("rt", ")"),
+ ("lc", "|"),
+ ("rc", "|"),
+ ("lb", "("),
+ ("rb", ")"),
+ ("lf", "|"),
+ ("rf", "|"),
+ ("lk", "|"),
+ ("rk", "|"),
+ ("bv", "|"),
+ ("ts", "s"),
+ ("br", "|"),
+ ("or", "|"),
+ ("ul", "_"),
+ ("rn", " "),
+ ("*p", "PI"),
+ ("**", "*"),
+};
+
+ Entity: adt {
+ name: string;
+ value: int;
+ };
+ Entities: array of Entity;
+
+Entities = array[] of {
+ Entity( "&#161;", '¡' ),
+ Entity( "&#162;", '¢' ),
+ Entity( "&#163;", '£' ),
+ Entity( "&#164;", '¤' ),
+ Entity( "&#165;", '¥' ),
+ Entity( "&#166;", '¦' ),
+ Entity( "&#167;", '§' ),
+ Entity( "&#168;", '¨' ),
+ Entity( "&#169;", '©' ),
+ Entity( "&#170;", 'ª' ),
+ Entity( "&#171;", '«' ),
+ Entity( "&#172;", '¬' ),
+ Entity( "&#173;", '­' ),
+ Entity( "&#174;", '®' ),
+ Entity( "&#175;", '¯' ),
+ Entity( "&#176;", '°' ),
+ Entity( "&#177;", '±' ),
+ Entity( "&#178;", '²' ),
+ Entity( "&#179;", '³' ),
+ Entity( "&#180;", '´' ),
+ Entity( "&#181;", 'µ' ),
+ Entity( "&#182;", '¶' ),
+ Entity( "&#183;", '·' ),
+ Entity( "&#184;", '¸' ),
+ Entity( "&#185;", '¹' ),
+ Entity( "&#186;", 'º' ),
+ Entity( "&#187;", '»' ),
+ Entity( "&#188;", '¼' ),
+ Entity( "&#189;", '½' ),
+ Entity( "&#190;", '¾' ),
+ Entity( "&#191;", '¿' ),
+ Entity( "&Agrave;", 'À' ),
+ Entity( "&Aacute;", 'Á' ),
+ Entity( "&Acirc;", 'Â' ),
+ Entity( "&Atilde;", 'Ã' ),
+ Entity( "&Auml;", 'Ä' ),
+ Entity( "&Aring;", 'Å' ),
+ Entity( "&AElig;", 'Æ' ),
+ Entity( "&Ccedil;", 'Ç' ),
+ Entity( "&Egrave;", 'È' ),
+ Entity( "&Eacute;", 'É' ),
+ Entity( "&Ecirc;", 'Ê' ),
+ Entity( "&Euml;", 'Ë' ),
+ Entity( "&Igrave;", 'Ì' ),
+ Entity( "&Iacute;", 'Í' ),
+ Entity( "&Icirc;", 'Î' ),
+ Entity( "&Iuml;", 'Ï' ),
+ Entity( "&ETH;", 'Ð' ),
+ Entity( "&Ntilde;", 'Ñ' ),
+ Entity( "&Ograve;", 'Ò' ),
+ Entity( "&Oacute;", 'Ó' ),
+ Entity( "&Ocirc;", 'Ô' ),
+ Entity( "&Otilde;", 'Õ' ),
+ Entity( "&Ouml;", 'Ö' ),
+ Entity( "&215;", '×' ),
+ Entity( "&Oslash;", 'Ø' ),
+ Entity( "&Ugrave;", 'Ù' ),
+ Entity( "&Uacute;", 'Ú' ),
+ Entity( "&Ucirc;", 'Û' ),
+ Entity( "&Uuml;", 'Ü' ),
+ Entity( "&Yacute;", 'Ý' ),
+ Entity( "&THORN;", 'Þ' ),
+ Entity( "&szlig;", 'ß' ),
+ Entity( "&agrave;", 'à' ),
+ Entity( "&aacute;", 'á' ),
+ Entity( "&acirc;", 'â' ),
+ Entity( "&atilde;", 'ã' ),
+ Entity( "&auml;", 'ä' ),
+ Entity( "&aring;", 'å' ),
+ Entity( "&aelig;", 'æ' ),
+ Entity( "&ccedil;", 'ç' ),
+ Entity( "&egrave;", 'è' ),
+ Entity( "&eacute;", 'é' ),
+ Entity( "&ecirc;", 'ê' ),
+ Entity( "&euml;", 'ë' ),
+ Entity( "&igrave;", 'ì' ),
+ Entity( "&iacute;", 'í' ),
+ Entity( "&icirc;", 'î' ),
+ Entity( "&iuml;", 'ï' ),
+ Entity( "&eth;", 'ð' ),
+ Entity( "&ntilde;", 'ñ' ),
+ Entity( "&ograve;", 'ò' ),
+ Entity( "&oacute;", 'ó' ),
+ Entity( "&ocirc;", 'ô' ),
+ Entity( "&otilde;", 'õ' ),
+ Entity( "&ouml;", 'ö' ),
+ Entity( "&247;", '÷' ),
+ Entity( "&oslash;", 'ø' ),
+ Entity( "&ugrave;", 'ù' ),
+ Entity( "&uacute;", 'ú' ),
+ Entity( "&ucirc;", 'û' ),
+ Entity( "&uuml;", 'ü' ),
+ Entity( "&yacute;", 'ý' ),
+ Entity( "&thorn;", 'þ' ),
+ Entity( "&yuml;", 'ÿ' ), # &#255;
+
+ Entity( "&#SPACE;", ' ' ),
+ Entity( "&#RS;", '\n' ),
+ Entity( "&#RE;", '\r' ),
+ Entity( "&quot;", '"' ),
+ Entity( "&amp;", '&' ),
+ Entity( "&lt;", '<' ),
+ Entity( "&gt;", '>' ),
+
+ Entity( "CAP-DELTA", 'Δ' ),
+ Entity( "ALPHA", 'α' ),
+ Entity( "BETA", 'β' ),
+ Entity( "DELTA", 'δ' ),
+ Entity( "EPSILON", 'ε' ),
+ Entity( "THETA", 'θ' ),
+ Entity( "MU", 'μ' ),
+ Entity( "PI", 'π' ),
+ Entity( "TAU", 'τ' ),
+ Entity( "CHI", 'χ' ),
+
+ Entity( "<-", '←' ),
+ Entity( "^", '↑' ),
+ Entity( "->", '→' ),
+ Entity( "v", '↓' ),
+ Entity( "!=", '≠' ),
+ Entity( "<=", '≤' ),
+ Entity( nil, 0 ),
+};
+
+
+Hit: adt {
+ glob: string;
+ chap: string;
+ mtype: string;
+ page: string;
+};
+
+Lnone, Lordered, Lunordered, Ldef, Lother: con iota; # list types
+
+Chaps: adt {
+ name: string;
+ primary: int;
+};
+
+Types: adt {
+ name: string;
+ desc: string;
+};
+
+
+# having two separate flags here allows for inclusion of old-style formatted pages
+# under a new-style three-level tree
+Oldstyle: adt {
+ names: int; # two-level directory tree?
+ fmt: int; # old internal formats: e.g., "B" font means "L"; name in .TH in all caps
+};
+
+Href: adt {
+ title: string;
+ chap: string;
+ mtype: string;
+ man: string;
+};
+
+# per-thread global data
+Global: adt {
+ bufio: Bufio;
+ bin: ref Bufio->Iobuf;
+ bout: ref Bufio->Iobuf;
+ topname: string; # name of the top level categories in the manual
+ chaps: array of Chaps; # names of top-level partitions of this manual
+ types: array of Types; # names of second-level partitions
+ oldstyle: Oldstyle;
+ mantitle: string;
+ mandir: string;
+ thisone: Hit; # man page we're displaying
+ mtime: int; # last modification time of thisone
+ href: Href; # hrefs of components of this man page
+ hits: array of Hit;
+ nhits: int;
+ list_type: int;
+ pm: string; # proprietary marking
+ def_goobie: string; # deferred goobie
+ sop: int; # output at start of paragraph?
+ sol: int; # input at start of line?
+ broken: int; # output at a break?
+ fill: int; # in fill mode?
+ pre: int; # in PRE block?
+ example: int; # an example active?
+ ipd: int; # emit inter-paragraph distance?
+ indents: int;
+ hangingdt: int;
+ curfont: string; # current font
+ prevfont: string; # previous font
+ lastc: int; # previous char from input scanner
+ def_sm: int; # amount of deferred "make smaller" request
+
+ mk_href_chap: fn(g: self ref Global, chap: string);
+ mk_href_man: fn(g: self ref Global, man: string, oldstyle: int);
+ mk_href_mtype: fn(g: self ref Global, chap, mtype: string);
+ dobreak: fn(g: self ref Global);
+ print: fn(g: self ref Global, s: string);
+ softbr: fn(g: self ref Global): string;
+ softp: fn(g: self ref Global): string;
+};
+
+
+usage()
+{
+ sys->fprint(stderr, "Usage: man2html file [section]\n");
+ raise "fail:usage";
+}
+
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ str = load String String->PATH;
+ dt = load Daytime Daytime->PATH;
+ g := Global_init();
+ if(args != nil)
+ args = tl args;
+ if(args == nil)
+ usage();
+ page := hd args;
+ args = tl args;
+ section := "1";
+ if(args != nil)
+ section = hd args;
+ hit := Hit ("", "man", section, page);
+ domanpage(g, hit);
+ g.bufio->g.bout.flush();
+}
+
+# remove markup from a string
+# doesn't handle nested/quoted delimiters
+demark(s: string): string
+{
+ t: string;
+ clean := true;
+ for (i := 0; i < len s; i++) {
+ case s[i] {
+ '<' =>
+ clean = false;
+ '>' =>
+ clean = true;
+ * =>
+ if (clean)
+ t[len t] = s[i];
+ }
+ }
+ return t;
+}
+
+
+#
+# Convert an individual man page to HTML and output.
+#
+domanpage(g: ref Global, man: Hit)
+{
+ file := man.page;
+ g.bin = g.bufio->open(file, Bufio->OREAD);
+ g.bout = g.bufio->fopen(sys->fildes(1), Bufio->OWRITE);
+ if (g.bin == nil) {
+ fprint(stderr, "Cannot open %s: %r\n", file);
+ return;
+ }
+ (err, info) := sys->fstat(g.bin.fd);
+ if (! err) {
+ g.mtime = info.mtime;
+ }
+ g.thisone = man;
+ while ((p := getnext(g)) != nil) {
+ c := p[0];
+ if (c == '.' && g.sol) {
+ if (g.pre) {
+ g.print("</PRE>");
+ g.pre = false;
+ }
+ dogoobie(g, false);
+ dohangingdt(g);
+ } else if (g.def_goobie != nil || g.def_sm != 0) {
+ g.bufio->g.bin.ungetc();
+ dogoobie(g, true);
+ } else if (c == '\n') {
+ g.print(p);
+ dohangingdt(g);
+ } else
+ g.print(p);
+ }
+ if (g.pm != nil) {
+ g.print("<BR><BR><BR><FONT SIZE=-2><CENTER>\n");
+ g.print(g.pm);
+ g.print("<BR></CENTER></FONT>\n");
+ }
+ closeall(g, 0);
+ rev(g, g.bin);
+}
+
+dogoobie(g: ref Global, deferred: int)
+{
+ # read line, translate special chars
+ line: string;
+ while ((token := getnext(g)) != "\n") {
+ if (token == nil)
+ return;
+ line += token;
+ }
+
+ # parse into arguments
+ argl, rargl: list of string; # create reversed version, then invert
+ while ((line = str->drop(line, " \t")) != nil)
+ if (line[0] == '"') {
+ (token, line) = split(line[1:], '"');
+ rargl = token :: rargl;
+ } else {
+ (token, line) = str->splitl(line, " \t");
+ rargl = token :: rargl;
+ }
+
+ if (rargl == nil && !deferred)
+ return;
+ for ( ; rargl != nil; rargl = tl rargl)
+ argl = hd rargl :: argl;
+
+ def_sm := g.def_sm;
+ if (deferred && def_sm > 0) {
+ g.print(sprint("<FONT SIZE=-%d>", def_sm));
+ if (g.def_goobie == nil)
+ argl = "dS" :: argl; # dS is our own local creation
+ }
+
+ subgoobie(g, argl);
+
+ if (deferred && def_sm > 0) {
+ g.def_sm = 0;
+ g.print("</FONT>");
+ }
+}
+
+subgoobie(g: ref Global, argl: list of string)
+{
+ if (g.def_goobie != nil) {
+ argl = g.def_goobie :: argl;
+ g.def_goobie = nil;
+ if (tl argl == nil)
+ return;
+ }
+
+ # the command part is at most two characters, but may be concatenated with the first arg
+ cmd := hd argl;
+ argl = tl argl;
+ if (len cmd > 2) {
+ cmd = cmd[0:2];
+ argl = cmd[2:] :: argl;
+ }
+
+ case cmd {
+
+ "B" or "I" or "L" or "R" =>
+ font(g, cmd, argl); # "R" macro implicitly generated by deferred R* macros
+
+ "BI" or "BL" or "BR" or
+ "IB" or "IL" or
+ "LB" or "LI" or
+ "RB" or "RI" or "RL" =>
+ altfont(g, cmd[0:1], cmd[1:2], argl, true);
+
+ "IR" or "LR" =>
+ anchor(g, cmd[0:1], cmd[1:2], argl); # includes man page refs ("IR" is old style, "LR" is new)
+
+ "dS" =>
+ printargs(g, argl);
+ g.print("\n");
+
+ "1C" or "2C" or "DT" or "TF" => # ignore these
+ return;
+
+ "P" or "PP" or "LP" =>
+ g_PP(g);
+
+ "EE" => g_EE(g);
+ "EX" => g_EX(g);
+ "HP" => g_HP_TP(g, 1);
+ "IP" => g_IP(g, argl);
+ "PD" => g_PD(g, argl);
+ "PM" => g_PM(g, argl);
+ "RE" => g_RE(g);
+ "RS" => g_RS(g);
+ "SH" => g_SH(g, argl);
+ "SM" => g_SM(g, argl);
+ "SS" => g_SS(g, argl);
+ "TH" => g_TH(g, argl);
+ "TP" => g_HP_TP(g, 3);
+
+ "br" => g_br(g);
+ "sp" => g_sp(g, argl);
+ "ti" => g_br(g);
+ "nf" => g_nf(g);
+ "fi" => g_fi(g);
+ "ft" => g_ft(g, argl);
+
+ * => return; # ignore unrecognized commands
+ }
+
+}
+
+g_br(g: ref Global)
+{
+ if (g.hangingdt != 0) {
+ g.print("<DD>");
+ g.hangingdt = 0;
+ } else if (g.fill && ! g.broken)
+ g.print("<BR>\n");
+ g.broken = true;
+}
+
+g_EE(g: ref Global)
+{
+ g.print("</PRE>\n");
+ g.fill = true;
+ g.broken = true;
+ g.example = false;
+}
+
+g_EX(g: ref Global)
+{
+ g.print("<PRE>");
+ if (! g.broken)
+ g.print("\n");
+ g.sop = true;
+ g.fill = false;
+ g.broken = true;
+ g.example = true;
+}
+
+g_fi(g: ref Global)
+{
+ if (g.fill)
+ return;
+ g.fill = true;
+ g.print("<P style=\"display: inline; white-space: normal\">\n");
+ g.broken = true;
+ g.sop = true;
+}
+
+g_ft(g: ref Global, argl: list of string)
+{
+ font: string;
+ arg: string;
+
+ if (argl == nil)
+ arg = "P";
+ else
+ arg = hd argl;
+
+ if (g.curfont != nil)
+ g.print(sprint("</%s>", g.curfont));
+
+ case arg {
+ "2" or "I" =>
+ font = "I";
+ "3" or "B" =>
+ font = "B";
+ "5" or "L" =>
+ font = "TT";
+ "P" =>
+ font = g.prevfont;
+ * =>
+ font = nil;
+ }
+ g.prevfont = g.curfont;
+ g.curfont = font;
+ if (g.curfont != nil)
+ if (g.fill)
+ g.print(sprint("<%s>", g.curfont));
+ else
+ g.print(sprint("<%s style=\"white-space: pre\">", g.curfont));
+}
+
+# level == 1 is a .HP; level == 3 is a .TP
+g_HP_TP(g: ref Global, level: int)
+{
+ case g.list_type {
+ Ldef =>
+ if (g.hangingdt != 0)
+ g.print("<DD>");
+ g.print(g.softbr() + "<DT>");
+ * =>
+ closel(g);
+ g.list_type = Ldef;
+ g.print("<DL compact>\n" + g.softbr() + "<DT>");
+ }
+ g.hangingdt = level;
+ g.broken = true;
+}
+
+g_IP(g: ref Global, argl: list of string)
+{
+ case g.list_type {
+
+ Lordered or Lunordered or Lother =>
+ ; # continue with an existing list
+
+ * =>
+ # figure out the type of a new list and start it
+ closel(g);
+ arg := "";
+ if (argl != nil)
+ arg = hd argl;
+ case arg {
+ "1" or "i" or "I" or "a" or "A" =>
+ g.list_type = Lordered;
+ g.print(sprint("<OL type=%s>\n", arg));
+ "*" or "•" or "&#8226;" =>
+ g.list_type = Lunordered;
+ g.print("<UL type=disc>\n");
+ "○" or "&#9675;"=>
+ g.list_type = Lunordered;
+ g.print("<UL type=circle>\n");
+ "□" or "&#9633;" =>
+ g.list_type = Lunordered;
+ g.print("<UL type=square>\n");
+ * =>
+ g.list_type = Lother;
+ g.print("<DL compact>\n");
+ }
+ }
+
+ # actually do this list item
+ case g.list_type {
+ Lother =>
+ g.print(g.softp()); # make sure there's space before each list item
+ if (argl != nil) {
+ g.print("<DT>");
+ printargs(g, argl);
+ }
+ g.print("\n<DD>");
+
+ Lordered or Lunordered =>
+ g.print(g.softp() + "<LI>");
+ }
+ g.broken = true;
+}
+
+g_nf(g: ref Global)
+{
+ if (! g.fill)
+ return;
+ g.fill = false;
+ g.print("<PRE>\n");
+ g.broken = true;
+ g.sop = true;
+ g.pre = true;
+}
+
+g_PD(g: ref Global, argl: list of string)
+{
+ if (len argl == 1 && hd argl == "0")
+ g.ipd = false;
+ else
+ g.ipd = true;
+}
+
+g_PM(g: ref Global, argl: list of string)
+{
+ code := "P";
+ if (argl != nil)
+ code = hd argl;
+ case code {
+ * => # includes "1" and "P"
+ g.pm = "<B>Lucent Technologies - Proprietary</B>\n" +
+ "<BR>Use pursuant to Company Instructions.\n";
+ "2" or "RS" =>
+ g.pm = "<B>Lucent Technologies - Proprietary (Restricted)</B>\n" +
+ "<BR>Solely for authorized persons having a need to know\n" +
+ "<BR>pursuant to Company Instructions.\n";
+ "3" or "RG" =>
+ g.pm = "<B>Lucent Technologies - Proprietary (Registered)</B>\n" +
+ "<BR>Solely for authorized persons having a need to know\n" +
+ "<BR>and subject to cover sheet instructions.\n";
+ "4" or "CP" =>
+ g.pm = "SEE PROPRIETARY NOTICE ON COVER PAGE\n";
+ "5" or "CR" =>
+ g.pm = "Copyright xxxx Lucent Technologies\n" + # should fill in the year from the date register
+ "<BR>All Rights Reserved.\n";
+ "6" or "UW" =>
+ g.pm = "THIS DOCUMENT CONTAINS PROPRIETARY INFORMATION OF\n" +
+ "<BR>LUCENT TECHNOLOGIES INC. AND IS NOT TO BE DISCLOSED OR USED EXCEPT IN\n" +
+ "<BR>ACCORDANCE WITH APPLICABLE AGREEMENTS.\n" +
+ "<BR>Unpublished & Not for Publication\n";
+ }
+}
+
+g_PP(g: ref Global)
+{
+ closel(g);
+ reset_font(g);
+ p := g.softp();
+ if (p != nil)
+ g.print(p);
+ g.sop = true;
+ g.broken = true;
+}
+
+g_RE(g: ref Global)
+{
+ g.print("</DL>\n");
+ g.indents--;
+ g.broken = true;
+}
+
+g_RS(g: ref Global)
+{
+ g.print("<DL>\n<DT><DD>");
+ g.indents++;
+ g.broken = true;
+}
+
+g_SH(g: ref Global, argl: list of string)
+{
+ closeall(g, 1); # .SH is top-level list item
+ if (g.example)
+ g_EE(g);
+ if (g.fill && ! g.sop)
+ g.print("<P>");
+ g.print("<DT><H4>");
+ printargs(g, argl);
+ g.print("</H4>\n");
+ g.print("<DD>\n");
+ g.sop = true;
+ g.broken = true;
+}
+
+g_SM(g: ref Global, argl: list of string)
+{
+ g.def_sm++; # can't use def_goobie, lest we collide with a deferred font macro
+ if (argl == nil)
+ return;
+ g.print(sprint("<FONT SIZE=-%d>", g.def_sm));
+ printargs(g, argl);
+ g.print("</FONT>\n");
+ g.def_sm = 0;
+}
+
+g_sp(g: ref Global, argl: list of string)
+{
+ if (g.sop && g.fill)
+ return;
+ count := 1;
+ if (argl != nil) {
+ rcount := real hd argl;
+ count = int rcount; # may be 0 (e.g., ".sp .5")
+ if (count == 0 && rcount > 0.0)
+ count = 1; # force whitespace for fractional lines
+ }
+ g.dobreak();
+ for (i := 0; i < count; i++)
+ g.print("&nbsp;<BR>\n");
+ g.broken = true;
+ g.sop = count > 0;
+}
+
+g_SS(g: ref Global, argl: list of string)
+{
+ closeall(g, 1);
+ g.indents++;
+ g.print(g.softp() + "<DL><DT><FONT SIZE=3><B>");
+ printargs(g, argl);
+ g.print("</B></FONT>\n");
+ g.print("<DD>\n");
+ g.sop = true;
+ g.broken = true;
+}
+
+g_TH(g: ref Global, argl: list of string)
+{
+ if (g.oldstyle.names && len argl > 2)
+ argl = hd argl :: hd tl argl :: nil; # ignore extra .TH args on pages in oldstyle trees
+ case len argl {
+ 0 =>
+ g.oldstyle.fmt = true;
+ title(g, sprint("%s", g.href.title), false);
+ 1 =>
+ g.oldstyle.fmt = true;
+ title(g, sprint("%s", hd argl), false); # any pages use this form?
+ 2 =>
+ g.oldstyle.fmt = true;
+ g.thisone.page = hd argl;
+ g.thisone.mtype = hd tl argl;
+ g.mk_href_man(hd argl, true);
+ g.mk_href_mtype(nil, hd tl argl);
+ title(g, sprint("%s(%s)", g.href.man, g.href.mtype), false);
+ * =>
+ g.oldstyle.fmt = false;
+ chap := hd tl tl argl;
+ g.mk_href_chap(chap);
+ g.mk_href_man(hd argl, false);
+ g.mk_href_mtype(chap, hd tl argl);
+ title(g, sprint("%s/%s/%s(%s)", g.href.title, g.href.chap, g.href.man, g.href.mtype), false);
+ }
+ g.print("[<a href=\"../index.html\">manual index</a>]");
+ g.print("[<a href=\"INDEX.html\">section index</a>]<p>");
+ g.print("<DL>\n"); # whole man page is just one big list
+ g.indents = 1;
+ g.sop = true;
+ g.broken = true;
+}
+
+dohangingdt(g: ref Global)
+{
+ case g.hangingdt {
+ 3 =>
+ g.hangingdt--;
+ 2 =>
+ g.print("<DD>");
+ g.hangingdt = 0;
+ g.broken = true;
+ }
+}
+
+# close a list, if there's one active
+closel(g: ref Global)
+{
+ case g.list_type {
+ Lordered =>
+ g.print("</OL>\n");
+ g.broken = true;
+ Lunordered =>
+ g.print("</UL>\n");
+ g.broken = true;
+ Lother or Ldef =>
+ g.print("</DL>\n");
+ g.broken = true;
+ }
+ g.list_type = Lnone;
+}
+
+closeall(g: ref Global, level: int)
+{
+ closel(g);
+ reset_font(g);
+ while (g.indents > level) {
+ g.indents--;
+ g.print("</DL>\n");
+ g.broken = true;
+ }
+}
+
+#
+# Show last revision date for a file.
+#
+rev(g: ref Global, filebuf: ref Bufio->Iobuf)
+{
+ if (g.mtime == 0) {
+ (err, info) := sys->fstat(filebuf.fd);
+ if (! err)
+ g.mtime = info.mtime;
+ }
+ if (g.mtime != 0) {
+ g.print("<P><TABLE width=\"100%\" border=0 cellpadding=10 cellspacing=0 bgcolor=\"#E0E0E0\">\n");
+ g.print("<TR>");
+ g.print(sprint("<TD align=left><FONT SIZE=-1>"));
+ g.print(sprint("%s(%s)", g.thisone.page, g.thisone.mtype));
+ g.print("</FONT></TD>\n");
+ g.print(sprint("<TD align=right><FONT SIZE=-1><I>Rev:&nbsp;&nbsp;%s</I></FONT></TD></TR></TABLE>\n",
+ dt->text(dt->gmt(g.mtime))));
+ }
+}
+
+#
+# Some font alternation macros are references to other man pages;
+# detect them (second arg contains balanced parens) and make them into hot links.
+#
+anchor(g: ref Global, f1, f2: string, argl: list of string)
+{
+ final := "";
+ link := false;
+ if (len argl == 2) {
+ (s, e) := str->splitl(hd tl argl, ")");
+ if (str->prefix("(", s) && e != nil) {
+ # emit href containing search for target first
+ # if numeric, do old style
+ link = true;
+ file := hd argl;
+ (chap, man) := split(httpunesc(file), '/');
+ if (man == nil) {
+ # given no explicit chapter prefix, use current chapter
+ man = chap;
+ chap = g.thisone.chap;
+ }
+ mtype := s[1:];
+ if (mtype == nil)
+ mtype = "-";
+ (n, toks) := sys->tokenize(mtype, "."); # Fix section 10
+ if (n > 1) mtype = hd toks;
+ g.print(sprint("<A href=\"../%s/%s.html\">", mtype, fixlink(man)));
+
+ #
+ # now generate the name the user sees, with terminal punctuation
+ # moved after the closing </A>.
+ #
+ if (len e > 1)
+ final = e[1:];
+ argl = hd argl :: s + ")" :: nil;
+ }
+ }
+ altfont(g, f1, f2, argl, false);
+ if (link) {
+ g.print("</A>");
+ font(g, f2, final :: nil);
+ } else
+ g.print("\n");
+}
+
+
+#
+# Fix up a link
+#
+
+fixlink(l: string): string
+{
+ ll := str->tolower(l);
+ if (ll == "copyright") ll = "1" + ll;
+ (a, b) := str->splitstrl(ll, "intro");
+ if (len b == 5) ll = a + "0" + b;
+ return ll;
+}
+
+
+#
+# output argl in font f
+#
+font(g: ref Global, f: string, argl: list of string)
+{
+ if (argl == nil) {
+ g.def_goobie = f;
+ return;
+ }
+ case f {
+ "L" => f = "TT";
+ "R" => f = nil;
+ }
+ if (f != nil) # nil == default (typically Roman)
+ g.print(sprint("<%s>", f));
+ printargs(g, argl);
+ if (f != nil)
+ g.print(sprint("</%s>", f));
+ g.print("\n");
+ g.prevfont = f;
+}
+
+#
+# output concatenated elements of argl, alternating between fonts f1 and f2
+#
+altfont(g: ref Global, f1, f2: string, argl: list of string, newline: int)
+{
+ reset_font(g);
+ if (argl == nil) {
+ g.def_goobie = f1;
+ return;
+ }
+ case f1 {
+ "L" => f1 = "TT";
+ "R" => f1 = nil;
+ }
+ case f2 {
+ "L" => f2 = "TT";
+ "R" => f2 = nil;
+ }
+ f := f1;
+ for (; argl != nil; argl = tl argl) {
+ if (f != nil)
+ g.print(sprint("<%s>%s</%s>", f, hd argl, f));
+ else
+ g.print(hd argl);
+ if (f == f1)
+ f = f2;
+ else
+ f = f1;
+ }
+ if (newline)
+ g.print("\n");
+ g.prevfont = f;
+}
+
+# not yet implemented
+map_font(nil: ref Global, nil: string)
+{
+}
+
+reset_font(g: ref Global)
+{
+ if (g.curfont != nil) {
+ g.print(sprint("</%s>", g.curfont));
+ g.prevfont = g.curfont;
+ g.curfont = nil;
+ }
+}
+
+printargs(g: ref Global, argl: list of string)
+{
+ for (; argl != nil; argl = tl argl)
+ if (tl argl != nil)
+ g.print(hd argl + " ");
+ else
+ g.print(hd argl);
+}
+
+# any parameter can be nil
+addhit(g: ref Global, chap, mtype, page: string)
+{
+ # g.print(sprint("Adding %s / %s (%s) . . .", chap, page, mtype)); # debug
+ # always keep a spare slot at the end
+ if (g.nhits >= len g.hits - 1)
+ g.hits = (array[len g.hits + 32] of Hit)[0:] = g.hits;
+ g.hits[g.nhits].glob = chap + " " + mtype + " " + page;
+ g.hits[g.nhits].chap = chap;
+ g.hits[g.nhits].mtype = mtype;
+ g.hits[g.nhits++].page = page;
+}
+
+Global.dobreak(g: self ref Global)
+{
+ if (! g.broken) {
+ g.broken = true;
+ g.print("<BR>\n");
+ }
+}
+
+Global.print(g: self ref Global, s: string)
+{
+ g.bufio->g.bout.puts(s);
+ if (g.sop || g.broken) {
+ # first non-white space, non-HTML we print takes us past the start of the paragraph & line
+ # (or even white space, if we're in no-fill mode)
+ for (i := 0; i < len s; i++) {
+ case s[i] {
+ '<' =>
+ while (++i < len s && s[i] != '>')
+ ;
+ continue;
+ ' ' or '\t' or '\n' =>
+ if (g.fill)
+ continue;
+ }
+ g.sop = false;
+ g.broken = false;
+ break;
+ }
+ }
+}
+
+Global.softbr(g: self ref Global): string
+{
+ if (g.broken)
+ return nil;
+ g.broken = true;
+ return "<BR>";
+}
+
+# provide a paragraph marker, unless we're already at the start of a section
+Global.softp(g: self ref Global): string
+{
+ if (g.sop)
+ return nil;
+ else if (! g.ipd)
+ return "<BR>";
+ if (g.fill)
+ return "<P>";
+ else
+ return "<P style=\"white-space: pre\">";
+}
+
+#
+# Get next logical character. Expand it with escapes.
+#
+getnext(g: ref Global): string
+{
+ iob := g.bufio;
+ Iobuf: import iob;
+
+ font: string;
+ token: string;
+ bin := g.bin;
+
+ g.sol = (g.lastc == '\n');
+
+ c := bin.getc();
+ if (c < 0)
+ return nil;
+ g.lastc = c;
+ if (c >= Runeself) {
+ for (i := 0; i < len Entities; i++)
+ if (Entities[i].value == c)
+ return Entities[i].name;
+ return sprint("&#%d;", c);
+ }
+ case c {
+ '<' =>
+ return "&lt;";
+ '>' =>
+ return "&gt;";
+ '\\' =>
+ c = bin.getc();
+ if (c < 0)
+ return nil;
+ g.lastc = c;
+ case c {
+
+ # chars to ignore
+ '|' or '&' or '^' =>
+ return getnext(g);
+
+ # ignore arg
+ 'k' =>
+ nil = bin.getc();
+ return getnext(g);
+
+ # defined strings
+ '*' =>
+ case bin.getc() {
+ 'R' =>
+ return "&#174;";
+ }
+ return getnext(g);
+
+ # special chars
+ '(' =>
+ token[0] = bin.getc();
+ token[1] = bin.getc();
+ for (i := 0; i < len tspec; i++)
+ if (token == tspec[i].name)
+ return tspec[i].value;
+ return "&#191;";
+ 'c' =>
+ c = bin.getc();
+ if (c < 0)
+ return nil;
+ else if (c == '\n') {
+ g.lastc = c;
+ g.sol = true;
+ token[0] = bin.getc();
+ return token;
+ }
+ # DEBUG: should there be a "return xxx" here?
+ 'e' =>
+ return "\\";
+ 'f' =>
+ g.lastc = c = bin.getc();
+ if (c < 0)
+ return nil;
+ case c {
+ '2' or 'I' =>
+ font = "I";
+ '3' or 'B' =>
+ font = "B";
+ '5' or 'L' =>
+ font = "TT";
+ 'P' =>
+ font = g.prevfont;
+ * => # includes '1' and 'R'
+ font = nil;
+ }
+# There are serious problems with this. We don't know the fonts properly at this stage.
+# g.prevfont = g.curfont;
+# g.curfont = font;
+# if (g.prevfont != nil)
+# token = sprint("</%s>", g.prevfont);
+# if (g.curfont != nil)
+# token += sprint("<%s>", g.curfont);
+ if (token == nil)
+ return " "; # shouldn't happen - maybe a \fR inside a font macro - just do something!
+ return token;
+ 's' =>
+ sign := '+';
+ size := 0;
+ relative := false;
+ getsize:
+ for (;;) {
+ c = bin.getc();
+ if (c < 0)
+ return nil;
+ case c {
+ '+' =>
+ relative = true;
+ '-' =>
+ sign = '-';
+ relative = true;
+ '0' to '9' =>
+ size = size * 10 + (c - '0');
+ * =>
+ bin.ungetc();
+ break getsize;
+ }
+ g.lastc = c;
+ }
+ if (size == 0)
+ token = "</FONT>";
+ else if (relative)
+ token = sprint("<FONT SIZE=%c%d>", sign, size);
+ else
+ token = sprint("<FONT SIZE=%d>", size);
+ return token;
+ }
+ }
+ token[0] = c;
+ return token;
+}
+
+#
+# Return strings before and after the left-most instance of separator;
+# (s, nil) if no match or separator is last char in s.
+#
+split(s: string, sep: int): (string, string)
+{
+ for (i := 0; i < len s; i++)
+ if (s[i] == sep)
+ return (s[:i], s[i+1:]); # s[len s:] is a valid slice, with value == nil
+ return (s, nil);
+}
+
+Global_init(): ref Global
+{
+ g := ref Global;
+ g.bufio = load Bufio Bufio->PATH;
+ g.chaps = array[20] of Chaps;
+ g.types = array[20] of Types;
+ g.mantitle = "";
+ g.href.title = g.mantitle; # ??
+ g.mtime = 0;
+ g.nhits = 0;
+ g.oldstyle.names = false;
+ g.oldstyle.fmt = false;
+ g.topname = "System";
+ g.list_type = Lnone;
+ g.def_sm = 0;
+ g.hangingdt = 0;
+ g.indents = 0;
+ g.sop = true;
+ g.broken = true;
+ g.ipd = true;
+ g.fill = true;
+ g.example = false;
+ g.pre = false;
+ g.lastc = '\n';
+ return g;
+}
+
+Global.mk_href_chap(g: self ref Global, chap: string)
+{
+ if (chap != nil)
+ g.href.chap = sprint("<A href=\"%s/%s?man=*\"><B>%s</B></A>", g.mandir, chap, chap);
+}
+
+Global.mk_href_man(g: self ref Global, man: string, oldstyle: int)
+{
+ rman := man;
+ if (oldstyle)
+ rman = str->tolower(man); # compensate for tradition of putting titles in all CAPS
+ g.href.man = sprint("<A href=\"%s?man=%s\"><B>%s</B></A>", g.mandir, rman, man);
+}
+
+Global.mk_href_mtype(g: self ref Global, chap, mtype: string)
+{
+ g.href.mtype = sprint("<A href=\"%s/%s/%s\"><B>%s</B></A>", g.mandir, chap, mtype, mtype);
+}
+
+# We assume that anything >= Runeself is already in UTF.
+#
+httpunesc(s: string): string
+{
+ t := "";
+ for (i := 0; i < len s; i++) {
+ c := s[i];
+ if (c == '&' && i + 1 < len s) {
+ (char, rem) := str->splitl(s[i+1:], ";");
+ if (rem == nil)
+ break; # require the terminating ';'
+ if (char == nil)
+ continue;
+ if (char[0] == '#' && len char > 1) {
+ c = int char[1:];
+ i += len char;
+ if (c < 256 && c >= 161) {
+ t[len t] = Entities[c-161].value;
+ continue;
+ }
+ } else {
+ for (j := 0; j < len Entities; j++)
+ if (Entities[j].name == char)
+ break;
+ if (j < len Entities) {
+ i += len char;
+ t[len t] = Entities[j].value;
+ continue;
+ }
+ }
+ }
+ t[len t] = c;
+ }
+ return t;
+}
+
+
+
+title(g: ref Global, t: string, search: int)
+{
+ if(search)
+ ; # not yet used
+ g.print("<HTML><HEAD>\n");
+ g.print(sprint("<TITLE>Inferno's %s</TITLE>\n", demark(t)));
+ g.print("</HEAD>\n");
+ g.print("<BODY bgcolor=\"#FFFFFF\">\n");
+
+}
diff --git a/appl/cmd/shutdown.b b/appl/cmd/shutdown.b
deleted file mode 100644
index 8eb7a86c..00000000
--- a/appl/cmd/shutdown.b
+++ /dev/null
@@ -1,72 +0,0 @@
-implement Shutdown;
-
-include "sys.m";
-sys: Sys;
-FD: import Sys;
-stderr: ref FD;
-
-include "draw.m";
-Context: import Draw;
-
-sysctl: con "/dev/sysctl";
-reboot: con "reboot";
-halt: con "halt";
-
-Shutdown: module
-{
- init: fn(ctxt: ref Context, argv: list of string);
-};
-
-rflag: int;
-hflag: int;
-
-init(nil: ref Context, argv: list of string)
-{
- sys = load Sys Sys->PATH;
-
- stderr = sys->fildes(2);
-
- argv = tl argv;
- if(len argv < 1)
- usage();
-
- while(argv != nil && len hd argv && (arg := hd argv)[0] == '-' && len arg > 1){
- case arg[1] {
- 'r' =>
- rflag = 1;
- 'h' =>
- hflag = 1;
- }
- argv = tl argv;
- }
-
- if(rflag == 0 && hflag == 0)
- usage();
-
- if(rflag == 1 && hflag == 1)
- usage();
-
- fd := sys->open(sysctl, sys->OWRITE);
- if(fd == nil) {
- sys->fprint(stderr, "shutdown: %r\n");
- exit;
- }
-
- if(rflag == 1)
- if (sys->write(fd, array of byte reboot, len reboot) < 0) {
- sys->fprint(stderr, "shutdown: write failed: %r\n");
- exit;
- }
-
- if(hflag == 1)
- if (sys->write(fd, array of byte halt, len halt) < 0) {
- sys->fprint(stderr, "shutdown: write failed: %r\n");
- exit;
- }
-}
-
-usage()
-{
- sys->fprint(stderr, "usage: shutdown -r | -h\n");
- exit;
-}
diff --git a/appl/lib/csv.b b/appl/lib/csv.b
new file mode 100644
index 00000000..336e2cde
--- /dev/null
+++ b/appl/lib/csv.b
@@ -0,0 +1,86 @@
+implement CSV;
+
+include "sys.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "csv.m";
+
+init(b: Bufio)
+{
+ bufio = b;
+}
+
+getline(fd: ref Iobuf): list of string
+{
+ rl: list of string;
+ for(;;){
+ (w, end) := getfield(fd);
+ if(rl == nil && w == nil && end < 0)
+ return nil;
+ rl = w :: rl;
+ if(end != ',')
+ break;
+ }
+ l: list of string;
+ for(; rl != nil; rl = tl rl)
+ l = hd rl :: l;
+ return l;
+}
+
+getfield(fd: ref Iobuf): (string, int)
+{
+ w := "";
+ if((c := getcr(fd)) == '"'){ # quoted field
+ while((c = getcr(fd)) >= 0){
+ if(c == '"'){
+ c = getcr(fd);
+ if(c != '"')
+ break;
+ }
+ w[len w] = c;
+ }
+ }
+ # unquoted text, possibly following quoted text above
+ for(; c >= 0 && c != ',' && c != '\n'; c = getcr(fd))
+ w[len w] = c;
+ return (w, c);
+}
+
+getcr(fd: ref Iobuf): int
+{
+ c := fd.getc();
+ if(c == '\r'){
+ nc := fd.getc();
+ if(nc >= 0 && nc != '\n')
+ fd.ungetc();
+ c = '\n';
+ }
+ return c;
+}
+
+quote(s: string): string
+{
+ sep := 0;
+ for(i := 0; i < len s; i++)
+ if((c := s[i]) == '"')
+ return innerquote(s);
+ else if(c == ',' || c == '\n')
+ sep = 1;
+ if(sep)
+ return "\""+s+"\"";
+ return s;
+}
+
+innerquote(s: string): string
+{
+ w := "\"";
+ for(i := j := 0; i < len s; i++)
+ if(s[i] == '"'){
+ w += s[j: i+1]; # including "
+ j = i; # including " again
+ }
+ return w+s[j:i]+"\"";
+}
diff --git a/appl/lib/rfc822.b b/appl/lib/rfc822.b
new file mode 100644
index 00000000..0f15a585
--- /dev/null
+++ b/appl/lib/rfc822.b
@@ -0,0 +1,561 @@
+implement RFC822;
+
+include "sys.m";
+ sys: Sys;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "rfc822.m";
+
+include "string.m";
+ str: String;
+
+include "daytime.m";
+ daytime: Daytime;
+ Tm: import daytime;
+
+Minrequest: con 512; # more than enough for most requests
+
+Suffix: adt {
+ suffix: string;
+ generic: string;
+ specific: string;
+ encoding: string;
+};
+
+SuffixFile: con "/lib/mimetype";
+mtime := 0;
+qid: Sys->Qid;
+
+suffixes: list of ref Suffix;
+
+nomod(s: string)
+{
+ raise sys->sprint("internal: can't load %s: %r", s);
+}
+
+init(b: Bufio)
+{
+ sys = load Sys Sys->PATH;
+ bufio = b;
+ str = load String String->PATH;
+ if(str == nil)
+ nomod(String->PATH);
+ daytime = load Daytime Daytime->PATH;
+ if(daytime == nil)
+ nomod(Daytime->PATH);
+ readsuffixfile();
+}
+
+readheaders(fd: ref Iobuf, limit: int): array of (string, array of byte)
+{
+ n := 0;
+ s := 0;
+ b := array[Minrequest] of byte;
+ nline := 0;
+ lines: list of array of byte;
+ while((c := fd.getb()) >= 0){
+ if(c == '\r'){
+ c = fd.getb();
+ if(c < 0)
+ break;
+ if(c != '\n'){
+ fd.ungetb();
+ c = '\r';
+ }
+ }
+ if(n >= len b){
+ if(len b >= limit)
+ return nil;
+ ab := array[n+512] of byte;
+ ab[0:] = b;
+ b = ab;
+ }
+ b[n++] = byte c;
+ if(c == '\n'){
+ if(n == 1 || b[n-2] == byte '\n')
+ break; # empty line
+ c = fd.getb();
+ if(c < 0)
+ break;
+ if(c != ' ' && c != '\t'){ # not continued
+ fd.ungetb();
+ lines = b[s: n] :: lines;
+ nline++;
+ s = n;
+ }else
+ b[n-1] = byte ' ';
+ }
+ }
+ if(n == 0)
+ return nil;
+ b = b[0: n];
+ if(n != s){
+ lines = b[s:n] :: lines;
+ nline++;
+ }
+ a := array[nline] of (string, array of byte);
+ for(; lines != nil; lines = tl lines){
+ b = hd lines;
+ name := "";
+ for(i := 0; i < len b; i++)
+ if(b[i] == byte ':'){
+ name = str->tolower(string b[0:i]);
+ b = b[i+1:];
+ break;
+ }
+ a[--nline] = (name, b);
+ }
+ return a;
+}
+
+#
+# *(";" parameter) used in transfer-extension, media-type and media-range
+# parameter = attribute "=" value
+# attribute = token
+# value = token | quoted-string
+#
+parseparams(ps: ref Rfclex): list of (string, string)
+{
+ l: list of (string, string);
+ do{
+ if(ps.lex() != Word)
+ break;
+ attr := ps.wordval;
+ if(ps.lex() != '=' || ps.lex() != Word && ps.tok != QString)
+ break;
+ l = (attr, ps.wordval) :: l;
+ }while(ps.lex() == ';');
+ ps.unlex();
+ return rev(l);
+}
+
+#
+# 1#transfer-coding
+#
+mimefields(ps: ref Rfclex): list of (string, list of (string, string))
+{
+ rf: list of (string, list of (string, string));
+ do{
+ if(ps.lex() == Word){
+ w := ps.wordval;
+ if(ps.lex() == ';'){
+ rf = (w, parseparams(ps)) :: rf;
+ ps.lex();
+ }else
+ rf = (w, nil) :: rf;
+ }
+ }while(ps.tok == ',');
+ ps.unlex();
+ f: list of (string, list of (string, string));
+ for(; rf != nil; rf = tl rf)
+ f = hd rf :: f;
+ return f;
+}
+
+# #(media-type | (media-range [accept-params])) ; Content-Type and Accept
+#
+# media-type = type "/" subtype *( ";" parameter )
+# type = token
+# subtype = token
+# LWS must not be used between type and subtype, nor between attribute and value (in parameter)
+#
+# media-range = ("*/*" | type "/*" | type "/" subtype ) *(";' parameter)
+# accept-params = ";" "q" "=" qvalue *( accept-extension )
+# accept-extension = ";" token [ "=" ( token | quoted-string ) ]
+#
+# 1#( ( charset | "*" )[ ";" "q" "=" qvalue ] ) ; Accept-Charset
+# 1#( codings [ ";" "q" "=" qvalue ] ) ; Accept-Encoding
+# 1#( language-range [ ";" "q" "=" qvalue ] ) ; Accept-Language
+#
+# codings = ( content-coding | "*" )
+#
+parsecontent(ps: ref Rfclex, multipart: int, head: list of ref Content): list of ref Content
+{
+ do{
+ if(ps.lex() == Word){
+ generic := ps.wordval;
+ specific := "*";
+ if(ps.lex() == '/'){
+ if(ps.lex() != Word)
+ break;
+ specific = ps.wordval;
+ if(!multipart && specific != "*")
+ break;
+ }else if(multipart)
+ break; # syntax error
+ else
+ ps.unlex();
+ params: list of (string, string) = nil;
+ if(ps.lex() == ';'){
+ params = parseparams(ps);
+ ps.lex();
+ }
+ head = Content.mk(generic, specific, params) :: head; # order reversed, but doesn't matter
+ }
+ }while(ps.tok == ',');
+ ps.unlex();
+ return head;
+}
+
+rev(l: list of (string, string)): list of (string, string)
+{
+ rl: list of (string, string);
+ for(; l != nil; l = tl l)
+ rl = hd l :: rl;
+ return rl;
+}
+
+Rfclex.mk(a: array of byte): ref Rfclex
+{
+ ps := ref Rfclex;
+ ps.fd = bufio->aopen(a);
+ ps.tok = '\n';
+ ps.eof = 0;
+ return ps;
+}
+
+Rfclex.getc(ps: self ref Rfclex): int
+{
+ c := ps.fd.getb();
+ if(c < 0)
+ ps.eof = 1;
+ return c;
+}
+
+Rfclex.ungetc(ps: self ref Rfclex)
+{
+ if(!ps.eof)
+ ps.fd.ungetb();
+}
+
+Rfclex.lex(ps: self ref Rfclex): int
+{
+ if(ps.seen != nil){
+ (ps.tok, ps.wordval) = hd ps.seen;
+ ps.seen = tl ps.seen;
+ }else
+ ps.tok = lex1(ps, 0);
+ return ps.tok;
+}
+
+Rfclex.unlex(ps: self ref Rfclex)
+{
+ ps.seen = (ps.tok, ps.wordval) :: ps.seen;
+}
+
+Rfclex.skipws(ps: self ref Rfclex): int
+{
+ return lex1(ps, 1);
+}
+
+#
+# rfc 2822/rfc 1521 lexical analyzer
+#
+lex1(ps: ref Rfclex, skipwhite: int): int
+{
+ ps.wordval = nil;
+ while((c := ps.getc()) >= 0){
+ case c {
+ '(' =>
+ level := 1;
+ while((c = ps.getc()) != Bufio->EOF && c != '\n'){
+ if(c == '\\'){
+ c = ps.getc();
+ if(c == Bufio->EOF)
+ return '\n';
+ continue;
+ }
+ if(c == '(')
+ level++;
+ else if(c == ')' && --level == 0)
+ break;
+ }
+ ' ' or '\t' or '\r' or 0 =>
+ ;
+ '\n' =>
+ return '\n';
+ ')' or '<' or '>' or '[' or ']' or '@' or '/' or ',' or
+ ';' or ':' or '?' or '=' =>
+ if(skipwhite){
+ ps.ungetc();
+ return c;
+ }
+ return c;
+
+ '"' =>
+ if(skipwhite){
+ ps.ungetc();
+ return c;
+ }
+ word(ps,"\"");
+ ps.getc(); # skip the closing quote
+ return QString;
+
+ * =>
+ ps.ungetc();
+ if(skipwhite)
+ return c;
+ word(ps,"\"()<>@,;:/[]?={}\r\n \t");
+ return Word;
+ }
+ }
+ return '\n';
+}
+
+# return the rest of an rfc 822 line, not including \r or \n
+# do not map to lower case
+
+Rfclex.line(ps: self ref Rfclex): string
+{
+ s := "";
+ while((c := ps.getc()) != Bufio->EOF && c != '\n' && c != '\r'){
+ if(c == '\\'){
+ c = ps.getc();
+ if(c == Bufio->EOF)
+ break;
+ }
+ s[len s] = c;
+ }
+ ps.tok = '\n';
+ ps.wordval = s;
+ return s;
+}
+
+word(ps: ref Rfclex, stop: string)
+{
+ w := "";
+ while((c := ps.getc()) != Bufio->EOF){
+ if(c == '\r')
+ c = ' ';
+ if(c == '\\'){
+ c = ps.getc();
+ if(c == Bufio->EOF)
+ break;
+ }else if(str->in(c,stop)){
+ ps.ungetc();
+ break;
+ }
+ if(c >= 'A' && c <= 'Z')
+ c += 'a' - 'A';
+ w[len w] = c;
+ }
+ ps.wordval = w;
+}
+
+readsuffixfile(): string
+{
+ iob := bufio->open(SuffixFile, Bufio->OREAD);
+ if(iob == nil)
+ return sys->sprint("cannot open %s: %r", SuffixFile);
+ for(n := 1; (line := iob.gets('\n')) != nil; n++){
+ (s, nil) := parsesuffix(line);
+ if(s != nil)
+ suffixes = s :: suffixes;
+ }
+ return nil;
+}
+
+parsesuffix(line: string): (ref Suffix, string)
+{
+ (line, nil) = str->splitstrl(line, "#");
+ if(line == nil)
+ return (nil, nil);
+ (n, slist) := sys->tokenize(line,"\n\t ");
+ if(n == 0)
+ return (nil, nil);
+ if(n < 4)
+ return (nil, "too few fields");
+ s := ref Suffix;
+ s.suffix = hd slist;
+ slist = tl slist;
+ s.generic = hd slist;
+ if (s.generic == "-")
+ s.generic = "";
+ slist = tl slist;
+ s.specific = hd slist;
+ if (s.specific == "-")
+ s.specific = "";
+ slist = tl slist;
+ s.encoding = hd slist;
+ if (s.encoding == "-")
+ s.encoding = "";
+ if((s.generic == nil || s.specific == nil) && s.encoding == nil)
+ return (nil, nil);
+ return (s, nil);
+}
+
+#
+# classify by file suffix
+#
+suffixclass(name: string): (ref Content, ref Content)
+{
+ typ, enc: ref Content;
+
+ p := str->splitstrr(name, "/").t1;
+ if(p != nil)
+ name = p;
+
+ for(;;){
+ (name, p) = suffix(name); # TO DO: match below is case sensitive
+ if(p == nil)
+ break;
+ for(l := suffixes; l != nil; l = tl l){
+ s := hd l;
+ if(p == s.suffix){
+ if(s.generic != nil && typ == nil)
+ typ = Content.mk(s.generic, s.specific, nil);
+ if(s.encoding != nil && enc == nil)
+ enc = Content.mk(s.encoding, "", nil);
+ if(typ != nil && enc != nil)
+ break;
+ }
+ }
+ }
+ return (typ, enc);
+}
+
+suffix(s: string): (string, string)
+{
+ for(n := len s; --n >= 0;)
+ if(s[n] == '.')
+ return (s[0: n], s[n:]);
+ return (s, nil);
+}
+
+#
+# classify by initial contents of file
+#
+dataclass(a: array of byte): (ref Content, ref Content)
+{
+ utf8 := 0;
+ for(i := 0; i < len a;){
+ c := int a[i];
+ if(c < 16r80){
+ if(c < 32 && c != '\n' && c != '\r' && c != '\t' && c != '\v' && c != '\f')
+ return (nil, nil);
+ i++;
+ }else{
+ utf8 = 1;
+ (r, l, nil) := sys->byte2char(a, i);
+ if(r == Sys->UTFerror)
+ return (nil, nil);
+ i += l;
+ }
+ }
+ if(utf8)
+ params := ("charset", "utf-8") :: nil;
+ return (Content.mk("text", "plain", params), nil);
+}
+
+Content.mk(generic, specific: string, params: list of (string, string)): ref Content
+{
+ c := ref Content;
+ c.generic = generic;
+ c.specific = specific;
+ c.params = params;
+ return c;
+}
+
+Content.check(me: self ref Content, oks: list of ref Content): int
+{
+ if(oks == nil)
+ return 1;
+ g := str->tolower(me.generic);
+ s := str->tolower(me.specific);
+ for(; oks != nil; oks = tl oks){
+ ok := hd oks;
+ if((ok.generic == g || ok.generic=="*") &&
+ (s == nil || ok.specific == s || ok.specific=="*"))
+ return 1;
+ }
+ return 0;
+}
+
+Content.text(c: self ref Content): string
+{
+ if((s := c.specific) != nil)
+ s = c.generic+"/"+s;
+ else
+ s = c.generic;
+ for(l := c.params; l != nil; l = tl l){
+ (n, v) := hd l;
+ s += sys->sprint(";%s=%s", n, quote(v));
+ }
+ return s;
+}
+
+#
+# should probably be in a Mime or HTTP module
+#
+
+Quotable: con "()<>@,;:\\\"/[]?={} \t";
+
+quotable(s: string): int
+{
+ for(i := 0; i < len s; i++)
+ if(str->in(s[i], Quotable))
+ return 1;
+ return 0;
+}
+
+quote(s: string): string
+{
+ if(!quotable(s))
+ return s;
+ q := "\"";
+ for(i := 0; i < len s; i++){
+ if(str->in(s[i], Quotable))
+ q[len q] = '\\';
+ q[len q] = s[i];
+ }
+ q[len q] = '"';
+ return q;
+}
+
+weekdays := array[] of {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+months := array[] of {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+# print dates in the format
+# Wkd, DD Mon YYYY HH:MM:SS GMT
+
+sec2date(t: int): string
+{
+ tm := daytime->gmt(t);
+ return sys->sprint("%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT",
+ weekdays[tm.wday], tm.mday, months[tm.mon], tm.year+1900,
+ tm.hour, tm.min, tm.sec);
+}
+
+# parse dates of formats
+# Wkd, DD Mon YYYY HH:MM:SS GMT
+# Weekday, DD-Mon-YY HH:MM:SS GMT
+# Wkd Mon ( D|DD) HH:MM:SS YYYY
+# plus anything similar
+
+date2sec(date: string): int
+{
+ tm := daytime->string2tm(date);
+ if(tm == nil || tm.year < 70 || tm.zone != "GMT")
+ t := 0;
+ else
+ t = daytime->tm2epoch(tm);
+ return t;
+}
+
+now(): int
+{
+ return daytime->now();
+}
+
+time(): string
+{
+ return sec2date(daytime->now());
+}
diff --git a/appl/lib/w3c/uris.b b/appl/lib/w3c/uris.b
new file mode 100644
index 00000000..b49c17b8
--- /dev/null
+++ b/appl/lib/w3c/uris.b
@@ -0,0 +1,320 @@
+implement URIs;
+
+#
+# RFC3986, URI Generic Syntax
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "string.m";
+ S: String;
+
+include "uris.m";
+
+Alpha: con "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+Digit: con "0123456789";
+
+GenDelims: con ":/?#[]@";
+SubDelims: con "!$&'()*+,;=";
+Reserved: con GenDelims + SubDelims;
+HexDigit: con Digit+"abcdefABCDEF";
+
+Escape: con GenDelims+"%"; # "%" must be encoded as %25
+
+Unreserved: con Alpha+Digit+"-._~";
+
+F_Esc, F_Scheme: con byte(1<<iota);
+
+ctype: array of byte;
+
+classify(s: string, f: byte)
+{
+ for(i := 0; i < len s; i++)
+ ctype[s[i]] |= f;
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ S = load String String->PATH;
+ if(S == nil)
+ raise sys->sprint("can't load %s: %r", String->PATH);
+
+ ctype = array [256] of { * => byte 0 };
+ classify(Escape, F_Esc);
+ for(i := 0; i <= ' '; i++)
+ ctype[i] |= F_Esc;
+ for(i = 16r80; i <= 16rFF; i++)
+ ctype[i] |= F_Esc;
+ classify(Alpha+Digit+"+-.", F_Scheme);
+}
+
+# scheme://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
+#
+# ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
+#
+# delimiters: :/?# /?# ?# #
+#
+URI.parse(url: string): ref URI
+{
+ scheme, userinfo, host, port, path, query, frag: string;
+ for(i := 0; i < len url; i++){
+ c := url[i];
+ if(c == ':'){
+ scheme = S->tolower(url[0:i]);
+ url = url[i+1:];
+ break;
+ }
+ if(c < 0 || c >= len ctype || (ctype[c] & F_Scheme) == byte 0)
+ break;
+ }
+
+ if(S->prefix("//", url)){
+ authority: string;
+ (authority, path) = S->splitstrl(url[2:], "/");
+ (up, hp) := splitl(authority, "@");
+ if(hp == "")
+ hp = authority;
+ else
+ userinfo = up;
+ if(hp != nil && hp[0] == '['){ # another rfc hack, for IPv6 addresses, which contain :
+ (host, hp) = S->splitstrr(hp, "]");
+ if(hp != nil && hp[0] == ':')
+ port = hp[1:];
+ else
+ host += hp; # put it back
+ }else
+ (host, port) = splitl(hp, ":");
+ if(path == nil)
+ path = "/";
+ }else
+ path = url;
+ (path, frag) = S->splitstrl(path, "#"); # includes # in frag
+ (path, query) = S->splitstrl(path, "?"); # includes ? in query
+ return ref URI(scheme, dec(userinfo), dec(host), port, dec(path), query, dec(frag));
+}
+
+URI.userpw(u: self ref URI): (string, string)
+{
+ return splitl(u.userinfo, ":");
+}
+
+URI.text(u: self ref URI): string
+{
+ s := "";
+ if(u.scheme != nil)
+ s += u.scheme + ":";
+ if(u.hasauthority())
+ s += "//" + u.authority();
+ return s + enc(u.path, "/@:") + u.query + enc1(u.fragment, "@:/?");
+}
+
+URI.copy(u: self ref URI): ref URI
+{
+ return ref *u;
+}
+
+URI.pathonly(u: self ref URI): ref URI
+{
+ v := ref *u;
+ v.userinfo = nil;
+ v.query = nil;
+ v.fragment = nil;
+ return v;
+}
+
+URI.addbase(u: self ref URI, b: ref URI): ref URI
+{
+ # RFC3986 5.2.2, rearranged
+ r := ref *u;
+ if(r.scheme == nil && b != nil){
+ r.scheme = b.scheme;
+ if(!r.hasauthority()){
+ r.userinfo = b.userinfo;
+ r.host = b.host;
+ r.port = b.port;
+ if(r.path == nil){
+ r.path = b.path;
+ if(r.query == nil)
+ r.query = b.query;
+ }else if(r.path[0] != '/'){
+ # 5.2.3: merge paths
+ if(b.path == "" && b.hasauthority())
+ p1 := "/";
+ else
+ (p1, nil) = S->splitstrr(b.path, "/");
+ r.path = p1 + r.path;
+ }
+ }
+ }
+ r.path = removedots(r.path);
+ return r;
+}
+
+URI.nodots(u: self ref URI): ref URI
+{
+ return u.addbase(nil);
+}
+
+URI.hasauthority(u: self ref URI): int
+{
+ return u.host != nil || u.userinfo != nil || u.port != nil;
+}
+
+URI.isabsolute(u: self ref URI): int
+{
+ return u.scheme != nil;
+}
+
+URI.authority(u: self ref URI): string
+{
+ s := enc(u.userinfo, ":");
+ if(s != nil)
+ s += "@";
+ if(u.host != nil){
+ s += enc(u.host, "[]:"); # assumes : appears inside []; could enforce it
+ if(u.port != nil)
+ s += ":" + enc(u.port,nil);
+ }
+ return s;
+}
+
+#
+# simplified version of procedure in RFC3986 5.2.4:
+# it extracts a complete segment from the input first, then analyses it
+#
+removedots(s: string): string
+{
+ if(s == nil)
+ return "";
+ out := "";
+ for(p := 0; p < len s;){
+ # extract the first segment and any preceding /
+ q := p;
+ if(++p < len s){
+ while(++p < len s && s[p] != '/')
+ {}
+ }
+ seg := s[q: p];
+ if((e := p) < len s)
+ e++;
+ case s[q: e] { # includes any following /
+ "../" or "./" => ;
+ "/./" or "/." =>
+ if(p >= len s)
+ s += "/";
+ "/../" or "/.." =>
+ if(p >= len s)
+ s += "/";
+ if(out != nil){
+ for(q = len out; --q > 0 && out[q] != '/';)
+ {} # skip
+ out = out[0: q];
+ }
+ "." or ".." => ; # null effect
+ * => # including "/"
+ out += seg;
+ }
+ }
+ return out;
+}
+
+#
+# similar to splitstrl but trims the matched character from the result
+#
+splitl(s, c: string): (string, string)
+{
+ (a, b) := S->splitstrl(s, c);
+ if(b != "")
+ b = b[1:];
+ return (a, b);
+}
+
+hex2(s: string): int
+{
+ n := 0;
+ for(i := 0; i < 2; i++){
+ if(i >= len s)
+ return -1;
+ n <<= 4;
+ case c := s[i] {
+ '0' to '9' =>
+ n += c-'0';
+ 'a' to 'f' =>
+ n += 10+(c-'a');
+ 'A' to 'F' =>
+ n += 10+(c-'A');
+ * =>
+ return -1;
+ }
+ }
+ return n;
+}
+
+dec(s: string): string
+{
+ for(i := 0;; i++){
+ if(i >= len s)
+ return s;
+ if(s[i] == '%' || s[i] == 0)
+ break;
+ }
+ o := s[0:i];
+ while(i < len s){
+ case c := s[i++] {
+ '%' =>
+ if((v := hex2(s[i:])) > 0){
+ c = v;
+ i += 2;
+ }
+ 0 =>
+ c = ' '; # shouldn't happen
+ }
+ o[len o] = c;
+ }
+ return o;
+}
+
+enc1(s: string, safe: string): string
+{
+ if(len s > 1)
+ return s[0:1] + enc(s[1:], safe);
+ return s;
+}
+
+# encoding depends on context (eg, &=/: not escaped in `query' string)
+enc(s: string, safe: string): string
+{
+ for(i := 0;; i++){
+ if(i >= len s)
+ return s; # use as-is
+ c := s[i];
+ if(c >= 16r80 || (ctype[c] & F_Esc) != byte 0 && !S->in(c, safe))
+ break;
+ }
+ t := s[0: i];
+ b := array of byte s[i:];
+ for(i = 0; i < len b; i++){
+ c := int b[i];
+ if((ctype[c] & F_Esc) != byte 0 && !S->in(c, safe))
+ t += sys->sprint("%%%.2X", c);
+ else
+ t[len t] = c;
+ }
+ return t;
+}
+
+URI.eq(u: self ref URI, v: ref URI): int
+{
+ if(v == nil)
+ return 0;
+ return u.scheme == v.scheme && u.userinfo == v.userinfo &&
+ u.host == v.host && u.port == v.port && u.path == v.path && # path might need canon
+ u.query == v.query; # not fragment
+}
+
+URI.eqf(u: self ref URI, v: ref URI): int
+{
+ return u.eq(v) && u.fragment == v.fragment;
+}
diff --git a/appl/spree/man/styxservers-nametree.man2 b/appl/spree/man/styxservers-nametree.man2
deleted file mode 100644
index f64e519a..00000000
--- a/appl/spree/man/styxservers-nametree.man2
+++ /dev/null
@@ -1,180 +0,0 @@
-.TH STYXSERVERS-NAMETREE 2
-.SH NAME
-Styxservers: nametree \-
-hierarchical name storage for use with Styxservers.
-.SH SYNOPSIS
-.EX
-include "sys.m";
-include "styx.m";
-include "styxservers.m";
-nametree := load Nametree Nametree->PATH;
- Tree: import nametree;
-
-Tree: adt {
- create: fn(t: self ref Tree, parentpath: big, d: Sys->Dir): string;
- remove: fn(t: self ref Tree, path: big): string;
- wstat: fn(t: self ref Tree, path: big, d: Sys->Dir);
- quit: fn(t: self ref Tree);
-};
-init: fn();
-start: fn(): (ref Tree, chan of ref Styxservers->Navop);
-.EE
-.SH DESCRIPTION
-.B Nametree
-provides the storage for a hierarchical namespace
-to be used by
-.IR styxservers (2).
-After the module is loaded, the
-.B init
-function should be called to
-initialise the module's internal variables.
-.B Start
-spawns a new
-.B nametree
-process; it returns a tuple, say
-.RI ( tree ,\ c ),
-where c is a channel that can be used to create
-an instance of
-.BR Styxservers->Navigator ,
-to access files inside
-.BR nametree ,
-and
-.I tree
-is an adt that allows creation and removal of those files.
-On failure, these functions return a string describing
-the error.
-.PP
-Note that the full set of operations on
-.B Nametree
-(i.e. stat, walk, readdir, wstate, create and remove),
-is only available in conjunction with
-.BR Styxserver 's
-.B Navigator
-interface.
-Files in the name space are ultimately identified by a 64-bit
-.I path
-value, which forms the path component of the file's Qid.
-(See
-.IR intro (5)
-for a description of the system's interpretation of Qids.)
-.PP
-The
-.B Tree
-operations
-are:
-.TP 10
-.IB t .create(\fIparentpath\fP,\ \fId\fP)
-Create a new file or directory.
-.I D
-gives the directory information that will be stored
-for the file, including its own path value,
-given by
-.IB d .qid.path .
-If the file referenced by
-.I parentpath
-does not exist, creation will not be allowed,
-other than in the special case when
-.IB d .qid.path
-is equal to
-.IR parentpath ,
-in which case it is assumed to be a root directory
-and may be created. This potentially allows a single
-.B Nametree
-instance to hold many distinct directory hierarchies.
-Note that no attempt is made to ensure that
-.I parentpath
-refers to a directory; the check is assumed to have
-been made previously.
-When a hierarchy is traversed,
-.B Nametree
-interprets the name
-.RB ` .. '
-itself as `parent directory', and that name should not be created explicitly.
-.TP
-.IB t .remove(\fIpath\fP)
-Remove the file referred to by
-.IR path ,
-and all its descendants.
-.TP
-.IB t .wstat(\fIpath\fP,\ \fId\fP)
-Change the directory information held on file
-.IR path .
-The Qid path itself cannot be changed by
-.IR d .
-.TP
-.IB t .quit()
-Shut down the
-.B nametree
-process.
-.SH EXAMPLE
-Here is a complete example that uses
-.B Nametree
-in conjunction with
-.B Styxservers
-in order to serve two files
-.B data
-and
-.BR ctl " ..."
-and do nothing with them:
-.EX
-implement Tst;
-include "sys.m";
- sys: Sys;
-include "draw.m";
-include "styx.m";
-include "styxservers.m";
- styxservers: Styxservers;
- Styxserver, Navigator: import styxservers;
- nametree: Nametree;
- Tree: import nametree;
-
-Tst: module
-{
- init: fn(nil: ref Draw->Context, argv: list of string);
-};
-
-Qroot, Qctl, Qdata: con big iota; # paths
-init(nil: ref Draw->Context, args: list of string)
-{
- sys = load Sys Sys->PATH;
- styx := load Styx Styx->PATH;
- styx->init();
- styxservers = load Styxservers Styxservers->PATH;
- styxservers->init(styx);
- nametree = load Nametree Nametree->PATH;
- nametree->init();
- sys->pctl(Sys->FORKNS, nil);
- (tree, treeop) := nametree->start();
- tree.create(Qroot, dir(".", 8r555|Sys->DMDIR, Qroot));
- tree.create(Qroot, dir("ctl", 8r666, Qctl));
- tree.create(Qroot, dir("data", 8r444, Qdata));
- (tchan, srv) := Styxserver.new(sys->fildes(0),
- Navigator.new(treeop), Qroot);
- while((gm := <-tchan) != nil) {
- # normally a pick on gm would act on
- # Tmsg.Read and Tmsg.Write at least
- srv.default(gm);
- }
- tree.quit();
-}
-
-dir(name: string, perm: int, qid: big): Sys->Dir
-{
- d := sys->zerodir;
- d.name = name;
- d.uid = "me";
- d.gid = "me";
- d.qid.path = qid;
- if (perm & Sys->DMDIR)
- d.qid.qtype = Sys->QTDIR;
- else
- d.qid.qtype = Sys->QTFILE;
- d.mode = perm;
- return d;
-}
-.EE
-.SH SOURCE
-.B /appl/lib/nametree.b
-.SH SEE ALSO
-.IR styxservers (2),
-.IR intro (5)
diff --git a/appl/spree/man/styxservers.man2 b/appl/spree/man/styxservers.man2
deleted file mode 100644
index fca2748a..00000000
--- a/appl/spree/man/styxservers.man2
+++ /dev/null
@@ -1,902 +0,0 @@
-.TH STYXSERVERS 2
-.SH NAME
-styxservers \-
-Styx server implementation assistance
-.SH SYNOPSIS
-.EX
-include "sys.m";
-include "styx.m";
-Tmsg, Rmsg: import Styx;
-include "styxservers.m";
-styxservers := load Styxservers Styxservers->PATH;
-Styxserver, Fid, Navigator: import styxservers;
-
-Styxserver: adt {
- fd: ref Sys->FD; # file server end of connection
- t: ref Navigator; # name space navigator for this server
- msize: int; # negotiated Styx message size
-
- new: fn(fd: ref Sys->FD, t: ref Navigator, rootpath: big)
- :(chan of ref Tmsg, ref Styxserver);
- reply: fn(srv: self ref Styxserver, m: ref Rmsg): int;
-
- # protocol operations
- attach: fn(srv: self ref Styxserver, m: ref Tmsg.Attach): ref Fid;
- clunk: fn(srv: self ref Styxserver, m: ref Tmsg.Clunk): ref Fid;
- walk: fn(srv: self ref Styxserver, m: ref Tmsg.Walk): ref Fid;
- open: fn(srv: self ref Styxserver, m: ref Tmsg.Open): ref Fid;
- read: fn(srv: self ref Styxserver, m: ref Tmsg.Read): ref Fid;
- remove: fn(srv: self ref Styxserver, m: ref Tmsg.Remove): ref Fid;
- stat: fn(srv: self ref Styxserver, m: ref Tmsg.Stat);
- default: fn(srv: self ref Styxserver, gm: ref Tmsg);
-
- # check validity
- cancreate: fn(srv: self ref Styxserver, m: ref Tmsg.Create)
- :(ref Fid, int, ref Sys->Dir, string);
- canopen: fn(srv: self ref Styxserver, m: ref Tmsg.Open)
- :(ref Fid, int, ref Sys->Dir, string);
- canread: fn(srv: self ref Styxserver, m: ref Tmsg.Read)
- :(ref Fid, string);
- canwrite: fn(srv: self ref Styxserver, m: ref Tmsg.Write)
- :(ref Fid, string);
-
- # fid management
- getfid: fn(srv: self ref Styxserver, fid: int): ref Fid;
- newfid: fn(srv: self ref Styxserver, fid: int): ref Fid;
- delfid: fn(srv: self ref Styxserver, c: ref Fid);
- allfids: fn(srv: self ref Styxserver): list of ref Fid;
-
- iounit: fn(srv: self ref Styxserver): int;
-};
-
-Fid: adt {
- fid: int; # client's fid
- path: big; # file's 64-bit unique path
- qtype: int; # file's qid type (eg, Sys->QTDIR if directory)
- isopen: int; # non-zero if file is open
- mode: int; # if open, the open mode
- uname: string; # user name from original attach
- param: string; # attach aname from original attach
- data: array of byte; # application data
-
- clone: fn(f: self ref Fid, nf: ref Fid): ref Fid;
- open: fn(f: self ref Fid, mode: int, qid: Sys->Qid);
- walk: fn(f: self ref Fid, qid: Sys->Qid);
-};
-
-Navop: adt {
- reply: chan of (ref Sys->Dir, string); # channel for reply
- path: big; # file or directory path
- pick {
- Stat =>
- Walk =>
- name: string;
- Readdir =>
- offset: int; # index (origin 0) of first entry to return
- count: int; # number of directory entries requested
- }
-};
-
-Navigator: adt {
- new: fn(c: chan of ref Navop): ref Navigator;
- stat: fn(t: self ref Navigator, path: big): (ref Sys->Dir, string);
- walk: fn(t: self ref Navigator, parent: big, name: string)
- : (ref Sys->Dir, string);
- readdir:fn(t: self ref Navigator, path: big,
- offset, count: int): array of ref Sys->Dir;
-};
-
-init: fn(styx: Styx);
-traceset: fn(on: int);
-
-readbytes: fn(m: ref Styx->Tmsg.Read, d: array of byte):
- ref Styx->Rmsg.Read;
-readstr: fn(m: ref Styx->Tmsg.Read, s: string):
- ref Styx->Rmsg.Read;
-openok: fn(uname: string, omode,
- perm: int, funame, fgname: string): int;
-openmode: fn(o: int): int;
-.EE
-.SH DESCRIPTION
-When writing a Styx file server, there are some
-commonly performed tasks that are
-fiddly or tedious to implement each time.
-.B Styxservers
-provides a framework to automate some of these
-routine tasks.
-In particular, it helps manage the fid space,
-implements common default processing for protocol messages,
-and assists walking around the
-directory hierarchy and reading of directories. Other
-tasks, such as defining the structure of the
-name space, and reading and writing files in it, are
-left to the file server program itself.
-Familiarity with Section 5 of the manual which defines the protocol
-(see
-.IR intro (5)),
-and with the representation of Styx messages in Limbo
-(see
-.IR styx (2)),
-is a prerequisite for use of this module.
-.PP
-.B Styxservers
-does not define or store any of the directory hierarchy itself;
-instead it queries an external process for information
-when necessary, through a value of type
-.BR Navigator ,
-which encapsulates communication with that process.
-That process must be started up
-independently of each
-.BR Styxserver ;
-a channel to such a process should be provided
-when starting a new
-.BR Styxserver .
-The channel carries messages of type
-.BR Navop .
-.IR Styxservers-nametree (2)
-provides a ready-made
-implementation of such a process that is sufficient for many applications.
-.PP
-.B Styxserver
-keeps tabs on the fids that are currently in use, and remembers
-some associated information, such as the Qid path
-of the file, whether it has been opened, etc.
-It does this using values of type
-.BR Fid .
-.PP
-Once the
-.B Styxservers
-module has been loaded,
-the
-.B init
-function must be called before anything else,
-to initialise its internal state. The
-.I styx
-argument should be an implementation of
-the
-.IR styx (2)
-module, which will be used to translate messages.
-Individual
-.B Styxserver
-instances do not share state, and are therefore
-independently thread-safe.
-.SS Fid representation
-.B Styxservers
-represents each active fid as a
-.B Fid
-value,
-which has the following public members:
-.TF param
-.TP
-.B fid
-The integer
-.I fid
-value provided by the client to refer to an active instance of a file in the file server,
-as described in
-.IR intro (5).
-.TP
-.B path
-The 64-bit qid path that uniquely identifies the file on the file server,
-as described in
-.IR intro (5).
-It is set by
-.IB f .walk
-and
-.IB f .open
-(see below).
-.TP
-.B qtype
-The file's qid type; it is
-.B Sys->QTDIR
-if and only if the fid refers to a directory.
-The value is set by
-.IB f .walk
-and
-.IB f .open
-(see below).
-.TP
-.B isopen
-Non-zero if and only if the fid has been opened by an
-.IR open (5)
-message.
-It is initially zero, and set by
-.IB f .open
-(see below).
-.TP
-.B mode
-Valid only if the fid has been opened.
-It has one of the values
-.BR Sys->OREAD ,
-.BR Sys->OWRITE ,
-.BR Sys->ORDWR ,
-possibly ORed with
-.BR Sys->ORCLOSE ,
-corresponding to the mode with which the file was opened.
-It is set by
-.IB f .open
-(see below).
-.TP
-.B uname
-The name of the user that created the fid.
-.TP
-.B param
-Set by
-.B Styxservers
-to the
-.B aname
-of the initial
-.IR attach (5)
-message,
-and subsequently inherited by each new fid created by
-.IR walk (5),
-but not otherwise used by
-.B Styxservers
-itself, and may be changed by the application.
-.TP
-.B data
-Unused by
-.BR Styxservers ;
-for application use.
-It might be used, for instance, to implement a file that gives different
-data to different clients.
-.TP
-.IB f .clone( nf )
-Copy the current state of all members of
-.I f
-except
-.IB f .fid\f1,\fP
-into
-.IR nf ,
-and return
-.IR nf .
-Used by
-.BR Styxserver.walk ,
-and is needed by an application only if it replaces that function.
-.TP
-.IB f .walk( qid )
-Make
-.I f
-refer to the file with the given
-.IR qid :
-set
-.IB f .path
-and
-.IB f .qtype
-from
-.IB qid .path
-and
-.IB qid .qtype .
-Used by
-.IB Styxserver.walk
-and is needed by an application only if it replaces that function.
-.TP
-.IB f .open( mode,\ qid )
-Mark
-.I f
-as `open',
-set
-.IR f .mode
-to
-.IR mode ,
-and set
-.B path
-and
-.B qtype
-to the path and type of
-.IR qid .
-Used by the
-implementations of
-.B open
-and
-.B create
-messages.
-The default implementation of
-.IR open (5)
-in
-.B Styxserver
-obtains the value of
-.I mode
-from
-.B Styxserver.canopen
-(below),
-and
-obtains the value of
-.I qid
-by querying the application's navigator.
-.SS Styxserver and file server state
-Each
-.B Styxserver
-value holds the state for a single file server, including its active fids,
-the link to the external name space process, and other internal data.
-Most of the state is manipulated through the member functions described below.
-The exceptions are two read-only values:
-the
-.B Navigator
-reference
-.IB srv .t
-which can be used to access that navigator; and
-the file descriptor
-.IB srv .fd
-that is the file server's end of the connection to the Styx client.
-Both values are initially provided by the file serving application,
-but can be accessed through the
-.B Styxserver
-value for convenience.
-The file descriptor value is normally used only through
-.BR Styxserver.reply ,
-but will be needed directly if the caller needs the file descriptor value
-as a parameter to
-.IR sys-pctl (2)
-when insulating the serving process's file descriptors from the surrounding environment.
-.PP
-The first set of functions in
-.B Styxserver
-provides common and default actions:
-.TP
-.B Styxserver.new(\fIfd\fP,\ \fIt\fP,\ \fIrootpath\fP)
-Create a new
-.BR Styxserver .
-It returns a tuple, say
-.RI ( c ", " srv ),
-and spawns a new process, which uses
-.IR styx (2)
-to read and parse Styx messages read
-from
-.IR fd ,
-and send them down
-.IR c ;
-.I t
-should be a
-.B Navigator
-adt which the
-.B Styxserver
-can use to answer queries
-on the name space (see ``Navigating file trees'', below).
-.I Rootpath
-gives the Qid path of the root of the served name space.
-.TP
-.IB srv .reply(\fIm\fP)
-Send a reply (R-message) to a client. The various utility methods,
-listed below, call this function to make their response.
-.TP
-.IB srv .attach(\fIm\fP)
-Respond to an
-.IR attach (5)
-message
-.IR m ,
-creating a new fid in the process, and returning it.
-Returns
-.B nil
-if
-.IB m .fid
-is a duplicate of an existing fid.
-The value of the attach parameter
-.IB m .aname
-is copied into the new fid's
-.B param
-field, as is the attaching user name,
-.IB m .uname .
-.TP
-.IB srv .clunk(\fIm\fP)
-Respond to a
-.IR clunk (5)
-message
-.IR m ,
-and return the old
-.BR Fid .
-Note that this does nothing about remove-on-close
-files; that should be programmed explicitly if needed.
-.TP
-.IB srv .walk(\fIm\fP)
-Respond to a
-.IR walk (5)
-message
-.IR m ,
-querying
-.IB srv . t
-for information on existing files.
-.TP
-.IB srv .open(\fIm\fP)
-Respond to an
-.IR open (5)
-message
-.IR m .
-This will allow a file to be opened if its permissions allow the
-specified mode of access.
-.TP
-.IB srv .read(\fIm\fP)
-Respond to a
-.IR read (5)
-message
-.IR m .
-If a directory is being read, the appropriate reply
-is made; for files, an error is given.
-.TP
-.IB srv .remove(\fIm\fP)
-Respond to a
-.IR remove (5)
-message
-.IR m
-with an error, clunking the fid as it does so,
-and returning the old
-.BR Fid .
-.TP
-.IB srv .stat(\fIm\fP)
-Respond to a
-.IR stat (5)
-message
-.IR m .
-.TP
-.IB srv .default(\fIgm\fP)
-Respond to an arbitrary T-message,
-.IR gm ,
-as appropriate (eg, by calling
-.IB srv .walk
-for a
-.IR walk (5)
-message).
-It responds appropriately to
-.IR version (5),
-and replies to
-.B Tauth
-(see
-.IR attach (5))
-stating that authentication is not required.
-Other messages without an associated
-.B Styxserver
-function are generally responded to
-with a ``permission denied'' error.
-.PP
-All the functions above check the validity of the fids, modes, counts and offsets
-in the messages, and automatically reply to the client with a suitable
-.IR error (5)
-message on error.
-.PP
-The following further
-.B Styxserver
-operations are useful
-in applications that override all or part of the default handling
-(in particular,
-to process read and write requests):
-.TP
-.IB srv .canopen( m )
-Check whether it is legal to open a file as requested by message
-.IR m :
-the fid is valid but not already open, the corresponding file exists and its
-permissions allow access in the requested mode, and if
-.B Sys->ORCLOSE
-is requested, the parent directory is writable (to allow the file to be removed when closed).
-.B Canopen
-returns a tuple, say
-.RI ( f ,\ mode ,\ d,\ err\ \fP).
-If the open request was invalid,
-.I f
-will be nil, and the string
-.I err
-will diagnose the error (for return to the client in an
-.B Rmsg.Error
-message).
-If the request was valid:
-.I f
-contains the
-.B Fid
-representing the file to be opened;
-.I mode
-is the access mode derived from
-.IB m .mode ,
-.BR Sys->OREAD ,
-.BR Sys->OWRITE ,
-.BR Sys->ORDWR ,
-ORed with
-.BR Sys->ORCLOSE ;
-.I d
-is a
-.B Dir
-value giving the file's attributes, obtained from the navigator;
-and
-.I err
-is nil.
-Once the application has done what it must to open the file,
-it must call
-.IB f .open
-to mark it open.
-.TP
-.IB srv .cancreate( m )
-Checks whether the
-creation of the file requested by
-message
-.I m
-is legal:
-the fid is valid but not open, refers to a directory,
-the permissions returned by
-.IR srv .t.stat
-show that directory is writable by the requesting user,
-the name does not already exist in that directory,
-and the mode with which the new file would be opened is valid.
-.B Cancreate
-returns a tuple, say
-.RI ( f ,\ mode,\ d,\ err\ \fP).
-If the creation request was invalid,
-.I f
-will be nil, and the string
-.I err
-will diagnose the error, for use in an error reply to the client.
-If the request was valid:
-.I f
-contains the
-.B Fid
-representing the parent directory;
-.I mode
-is the open mode as defined for
-.B canopen
-above;
-.I d
-is a
-.B Dir
-value containing some initial attributes for the new file or directory;
-and
-.I err
-is nil.
-The initial attributes set in
-.I d
-are:
-.IB d .name
-(the name of the file to be created);
-.IB d .uid
-and
-.IB d .muid
-(the user that did the initial attach);
-.IB d .gid ,
-.IB d .dtype ,
-.IB d .dev
-(taken from the parent directory's attributes);
-and
-.IB d .mode
-holds the file mode that should be attributed to the new
-file (taking into account the parent mode, as
-described in
-.IR open (5)).
-The caller must supply
-.IB d .qid
-once the file has successfully been created,
-and
-.IB d .atime
-and
-.IB d .mtime ;
-it must also call
-.IB f .open
-to mark
-.I f
-open and set its path to the file's path.
-If the file cannot be created successfully, the application should reply with
-an
-.IR error (5)
-message and leave
-.I f
-untouched.
-The
-.B Fid
-.I f
-will then continue to refer to the original directory, and remain unopened.
-.TP
-.IB srv .canread( m )
-Checks whether
-.IR read (5)
-message
-.I m
-refers to a valid fid that has been opened for reading,
-and that the count and file offset are non-negative.
-.B Canread
-returns a tuple, say
-.RI ( f ,\ err );
-if the attempted access is illegal,
-.I f
-will be nil, and
-.I err
-contains a description of the error,
-otherwise
-.I f
-contains the
-.B Fid
-corresponding to the file in question.
-It is typically called by an application's implementation of
-.B Tmsg.Read
-to obtain the
-.B Fid
-corresponding to the fid in the message, and check the access.
-.TP
-.IB srv .canwrite( m )
-Checks whether
-message
-.I m
-refers to a valid fid that has been opened for writing,
-and that the file offset is non-negative.
-.B Canwrite
-returns a tuple, say
-.RI ( f ,\ err );
-if the attempted access is illegal,
-.I f
-will be nil, and
-.I err
-contains a description of the error,
-otherwise
-.I f
-contains the
-.B Fid
-corresponding to the file in question.
-It is typically called by an application's implementation of
-.B Tmsg.Write
-to obtain the
-.B Fid
-corresponding to the fid in the message, and check the access.
-.TP
-.IB srv .iounit()
-Return an appropriate value for use as the
-.I iounit
-element in
-.B Rmsg.Open
-and
-.B Rmsg.Create
-replies,
-as defined in
-.IR open (5),
-based on the message size negotiated by the initial
-.IR version (5)
-message.
-.PP
-The remaining functions are normally used only by servers that need to
-override default actions.
-They maintain and access the mapping between a client's fid values presented in
-.B Tmsg
-messages and the
-.B Fid
-values that represent the corresponding files internally.
-.TP
-.IB srv .newfid(\fIfid\fP)
-Create a new
-.B Fid
-associated with number
-.I fid
-and return it.
-Return nil if the
-.I fid
-is already in use (implies a client error if the server correctly clunks fids).
-.TP
-.IB srv .getfid(\fIfid\fP)
-Get the
-.B Fid
-data associated with numeric id
-.IR fid ;
-return nil if there is none such (a malicious or erroneous client
-can cause this).
-.TP
-.IB srv .delfid(\fIfid\fP)
-Delete
-.I fid
-from the table of fids in the
-.BR Styxserver .
-(There is no error return.)
-.TP
-.IB srv .allfids()
-Return a list of all current fids (ie, the files currently active on the client).
-.PP
-.B Newfid
-is required when processing
-.IR auth (5),
-.IR attach (5)
-and
-.IR walk (5)
-messages to create new fids.
-.B Delfid
-is used to clunk fids when processing
-.IR clunk (5),
-.IR remove (5),
-and in a failed
-.IR walk (5)
-when it specified a new fid.
-All other messages should refer only to already existing fids, and the associated
-.B Fid
-data is fetched by
-.BR getfid .
-.SS Navigating file trees
-When a
-.B Styxserver
-instance needs to know about the namespace,
-it queries an external process through a channel
-by sending a
-.B Navop
-request;
-each such request carries with it a
-.B reply
-channel through which the
-reply should be made.
-The reply tuple has a reference to a
-.B Sys->Dir
-value that is non-nil on success, and a diagnostic string
-that is non-nil on error.
-.PP
-Files in the tree are referred to
-by their Qid
-.BR path .
-The requests are:
-.TF Walk
-.TP
-.BR Stat
-.br
-Find a file in the hierarchy by its
-.BR path ,
-and reply with the corresponding
-.B Dir
-data if found (or a diagnostic on error).
-.TP
-.BR Walk
-.br
-Look for file
-.B name
-in the directory with the given
-.BR path .
-.TP
-.BR Readdir
-.br
-Get information on selected files in the directory with the given
-.BR path .
-In this case, the reply channel is used to send
-a sequence of values, one for each entry in the directory, finishing with a tuple value
-.BR (nil,nil) .
-The entries to return are those selected by an
-.B offset
-that is the index (origin 0) of the first directory entry to return,
-and a
-.B count
-of a number of entries to return starting with that index.
-Note that both values are expressed in units of directory entries, not as byte counts.
-.PP
-.B Styxserver
-provides a
-.B Navigator
-adt to enable convenient access to this functionality; calls
-into the
-.B Navigator
-adt are bundled up into requests on the channel, and the
-reply returned.
-The functions provided are:
-.TP 10
-.BI Navigator.new( c )
-Create a new
-.BR Navigator ,
-sending requests down
-.IR c .
-.TP
-.IB t .stat(\fIpath\fP)
-Find the file with the given
-.IR path .
-Return a tuple
-.RI ( d ,\ err ),
-where
-.I d
-holds directory information for the file
-if found; otherwise
-.I err
-contains an error message.
-.TP
-.IB t .walk(\fIparent\fP,\ \fIname\fP)
-Find the file with name
-.I name
-inside parent directory
-.IR parent .
-Return a tuple as for
-.BR stat .
-.TP
-.IB t .readdir(\fIpath\fP,\ \fIoffset\fP,\ \fIcount\fP)
-Return directory data read from directory
-.IR path ,
-starting at entry
-.I offset
-for
-.I count
-entries.
-.SS Other functions
-The following functions provide some commonly used functionality:
-.TP 10
-.BI readbytes( m ,\ d )
-Assuming that the file in question contains data
-.IR d ,
-.B readbytes
-returns an appropriate reply to
-.IR read (5)
-message
-.IR m ,
-taking account of
-.IB m .offset
-and
-.IB m.count
-when extracting data from
-.IR d .
-.TP 10
-.BI readstr( m ,\ s )
-Assuming that the file in question contains string
-.IR s ,
-.B readstr
-returns an appropriate reply to
-.IR read (5)
-message
-.IR m ,
-taking account of
-.IB m .offset
-and
-.IB m.count
-when extracting data from the UTF-8 representation of
-.IR s .
-.TP
-.BI openok (\fIuname\fP,\ \fIomode\fP,\ \fIperm\fP,\ \fIfuid\fP,\ \fIfgid\fP)
-Does standard permission checking, assuming user
-.I uname
-is trying to open a file with access mode
-.IR omode ,
-where the file is owned by
-.IR fuid ,
-has group
-.IR fgid ,
-and permissions
-.IR perm .
-Returns true (non-zero) if permission would be granted, and false (zero) otherwise.
-.TP
-.BI openmode( o )
-Checks to see whether the open mode
-.I o
-is well-formed; if it is not,
-.B openmode
-returns -1; if it is, it returns the mode
-with OTRUNC and ORCLOSE flags removed.
-.TP
-.BI traceset( on )
-If
-.I on
-is true (non-zero),
-will trace Styx requests and replies, on standard error.
-This option must be set before creating a
-.BR Styxserver ,
-to ensure that it preserves its standard error descriptor.
-.SS Constants
-.B Styxservers
-defines a number of constants applicable to the writing
-of Styx servers, including:
-.TP
-.BR Einuse\fP,\fP\ Ebadfid\fP,\fP\ Eopen\fP,\fP\ Enotfound\fP,\fP\ Enotdir\fP,\fP\ Eperm\fP,\fP\ Ebadarg\fP,\fP\ Eexists
-These provide standard strings for commonly used error conditions,
-to be used in
-.B Rmsg.Error
-replies.
-.SS Authentication
-If authentication is required beyond that provided at the link level
-(for instance by
-.IR security-auth (2)),
-the server application must handle
-.B Tauth
-itself,
-remember the value of
-.I afid
-in that message, and generate an
-.B Rauth
-reply with a suitable Qid referring to a file with
-.B Qid.qtype
-of
-.BR QTAUTH .
-Following successful authentication by read and write on that file,
-it must associate that status with the
-.IR afid .
-Then, on a subsequent
-.B Tattach
-message, before calling
-.I srv .attach
-it must check that the
-.BR Tattach 's
-.I afid
-value corresponds to one previously authenticated, and
-reply with an appropriate error if not.
-.SH SOURCE
-.B /appl/lib/styxservers.b
-.SH SEE ALSO
-.IR styxservers-nametree (2),
-.IR sys-stat (2),
-.IR intro (5)
diff --git a/lib/mimetype b/lib/mimetype
new file mode 100644
index 00000000..45cdb78d
--- /dev/null
+++ b/lib/mimetype
@@ -0,0 +1,157 @@
+#suffix generic type specific type encoding safe?
+.C text plain - y # C++ program
+.Z - - compress m
+.a application octet-stream - y
+.ada text plain - y # ada program
+.ai application postscript - y
+.aif audio x-aiff - y
+.aifc audio x-aiff - y
+.aiff audio x-aiff - y
+.asf video x-ms-asf - m # MS streaming
+.asx video x-ms-asf - m # MS streaming
+.au audio basic - y # sun audio
+.avi video x-msvideo - m
+.awk text plain - y # awk program
+.bas text plain - y # basic program
+.bat application octet-stream - r # DOS executable
+.bbl text plain - y # BibTex output
+.bcpio application x-bcpio - m
+.bib text plain - y # BibTex input
+.bmp image bmp - y # bitmapped image
+.c text plain - y # C program
+.c++ text plain - y # C++ program
+.cacert application x-x509-ca-cert - y # DER X.509 CA certificate
+.cc text plain - y
+.cdf application x-netcdf - y
+.class application java - y # Java bytecodes
+.com application octet-stream - r # DOS executable
+.cpio application x-cpio - y
+.cpp text plain - y # DOS C++ program
+.crt application x-x509-ca-cert - y # DER X.509 CA certificate
+.css text css - m
+.csv application vnd.ms-excel - y # Microsoft Excel comma-separated-values
+.dat text plain - y # AMPL et al.
+.diff text plain - y
+.doc application msword - n # Microsoft Word
+.dvi application x-dvi - y # TeX output
+.enc application octet-stream - y # encrypted file
+.eps application postscript - y
+.etx text x-setext - m
+.exe application octet-stream - r # DOS executable
+.executable application octet-stream - r # DOS executable
+.exz application octet-stream gzip n # gzipped DOS executable
+.f text plain - y # fortran-77 program
+.fm application framemaker - y
+.f90 text plain - y # fortran-90 program
+.flc video x-flc - m
+.fli video x-fli - m
+.gif image gif - y
+.gtar application x-gtar - m
+.gz - - gzip m # gzipped file
+.h text plain - y # C header file
+.hdf application x-hdf - y
+.hqx application octet-stream - m # Mac BinHex
+.htm text html - m
+.html text html - m
+.ico image x-icon - y
+.ief image ief - y
+.jar application java-archive - y
+.jfif image jpeg - y
+.jfif-tbnl image jpeg - y
+.jpe image jpeg - y
+.jpeg image jpeg - y
+.jpg image jpeg - y
+.jpg image pjpeg - y
+.latex application x-latex - y
+.ltx application x-latex - y
+.man application x-troff-man - y
+.me application x-troff-me - y
+.mid audio midi - y # MIDI music
+.mime message rfc822 - y
+.mod text plain - y # AMPL et al.
+.mov video quicktime - y
+.movie video x-sgi-movie - y
+.mpe video mpeg - y
+.mpeg video mpeg - y
+.mpg video mpeg - y
+.ms application x-troff-ms - y
+.mv video x-sgi-movie - y
+.nc application x-netcdf - y
+.o application octet-stream - y
+.oda application oda - m
+.p text plain - y # Pascal program
+.p7s application x-pkcs7-signature - y # SMIME
+.pbm image x-portable-bitmap - y
+.pdf application pdf - y # Adobe Portable Document Format
+.pif application octet-stream - r # DOS executable
+.pgm image x-portable-graymap - y
+.pl text plain - y
+.pnm image x-portable-anymap - y
+.ppm image x-portable-pixmap - y
+.ppt application vnd.ms-powerpoint - n # Microsoft PowerPoint
+.ps application postscript - m
+.qcp audio vnd.qcelp - y # Qualcomm CELP
+.qcp2 audio qcp - y # Qualcomm CELP
+.qt video quicktime - y
+.r text plain - y # ratfor program
+.ra audio x-pn-realaudio - y # G2 RealAudio
+.ram audio x-pn-realaudio - y # G2 RealAudio
+.ras image x-cmu-rast - y
+.rc text plain - y # rc
+.rfr text plain - y # refer
+.rgb image x-rgb - y
+.rm application x-pn-realmedia - y # G2 RealAudio
+.roff application x-troff - y
+.rpm audio x-pn-realaudio-plugin - y # G2 RealAudio
+.rtf application rtf - y
+.rtx text richtext - y
+.scr application octet-stream - r # DOS executable (screen saver)
+.sh application x-shar - m
+.shar application x-shar - m
+.smi application smil - m # sync multimedia
+.smil application smil - m # sync multimedia
+.snd audio basic - y
+.suspect application octet-stream - y # upas/vf
+.sv4cpio application x-sv4cpio - m
+.sv4crc application x-sv4crc - y
+.t application x-troff - y
+.tar application x-tar - m
+.tardist application x-tardist - n # SGI SoftwareManager
+.taz application x-tar compress m
+.tcl application x-tcl - y
+.tex application x-tex - y # Tex input
+.texi application x-texinfo - y
+.texinfo application x-texinfo - y
+.text text plain - y
+.tgz application x-tar gzip m
+.tif image tiff - y
+.tiff image tiff - y
+.toc text plain - y # table of contents
+.tr application x-troff - y
+.trz application x-tar compress m
+.tsv text tab-separated-values - y
+.txt text - - y
+.txt text plain - y
+.ucert application x-x509-user-cert - y # DER X.509 user certificate
+.ustar application x-ustar - m
+.vcf text x-vcard - y # vCard
+.wav audio x-wav - y
+.wbmp image vnd.wap.wbmp - y # wireless bitmap
+.wml text vnd.wap.wml - m # WML doc
+.wmlc application vnd.wap.wmlc - m # compiled WML doc
+.wmls text vnd.wap.wmlscript - m # WMLScript
+.wmlsc application vnd.wap.wmlscriptc - m # compiled WMLScript
+.wsrc application x-wais-source - y
+.xbm image x-xbitmap - y # X bitmap
+.xgz - - x-gzip m # gzipped file
+.xls application vnd.ms-excel - n # Microsoft Excel
+.xml text xml - m
+.xpm image x-xpixmap - y
+.xwd image x-xwindowdump - y
+.z - - compress m
+.Z - - compress m
+.zip application zip - n
+.zzz application sleep - n # testing
+- application x-gunzip - p # type for .tar.gz
+- message delivery-status - y # mail bounces
+- application pgp-signature - y
diff --git a/lib/mk/binds b/lib/mk/binds
new file mode 100644
index 00000000..34d4b2ae
--- /dev/null
+++ b/lib/mk/binds
@@ -0,0 +1,2 @@
+/lib/mk/mkconfig /mkconfig
+/lib/mk/mksubdirs /mkfiles/mksubdirs
diff --git a/lib/mk/mkconfig b/lib/mk/mkconfig
new file mode 100644
index 00000000..17d31a93
--- /dev/null
+++ b/lib/mk/mkconfig
@@ -0,0 +1,28 @@
+#
+# Set the following 4 variables. The host system is the system where
+# the software will be built; the target system is where it will run.
+# They are almost always the same.
+
+# On Nt systems, the ROOT path MUST be of the form `drive:/path'
+ROOT=
+
+#
+# Except for building kernels, SYSTARG must always be the same as SYSHOST
+#
+SYSHOST=Plan9 # build system OS type (Hp, Inferno, Irix, Linux, Nt, Plan9, Solaris)
+SYSTARG=$SYSHOST # target system OS type (Hp, Inferno, Irix, Linux, Nt, Plan9, Solaris)
+
+#
+# specify the architecture of the target system - Inferno imports it from the
+# environment; for other systems it is usually just hard-coded
+#
+#OBJTYPE=386 # target system object type (s800, mips, 386, arm, sparc)
+OBJTYPE=386
+
+#
+# no changes required beyond this point
+#
+OBJDIR=$SYSTARG/$OBJTYPE
+
+<$ROOT/mkfiles/mkhost-$SYSHOST # variables appropriate for host system
+<$ROOT/mkfiles/mkfile-$SYSTARG-$OBJTYPE # variables used to build target object type
diff --git a/lib/mk/mksubdirs b/lib/mk/mksubdirs
new file mode 100644
index 00000000..9b076930
--- /dev/null
+++ b/lib/mk/mksubdirs
@@ -0,0 +1,13 @@
+all:V: all-$SHELLTYPE
+install:V: install-$SHELLTYPE
+uninstall:V: uninstall-$SHELLTYPE
+nuke:V: nuke-$SHELLTYPE
+clean:V: clean-$SHELLTYPE
+
+%-rc %-nt %-sh:QV:
+ for j in $DIRS {
+ if { ftest -d $j } {
+ echo 'cd' $j '; mk' $MKFLAGS $stem
+ cd $j; mk $MKFLAGS $stem; cd ..
+ }
+ }
diff --git a/lib9/getcallerpc-Linux-arm.S b/lib9/getcallerpc-Linux-arm.S
new file mode 100644
index 00000000..44c8e627
--- /dev/null
+++ b/lib9/getcallerpc-Linux-arm.S
@@ -0,0 +1 @@
+/* getcallerpc for arm Linux is placed in lib9.h as inline function*/
diff --git a/lib9/getcallerpc-MacOSX-386.s b/lib9/getcallerpc-MacOSX-386.s
new file mode 100644
index 00000000..92f940d7
--- /dev/null
+++ b/lib9/getcallerpc-MacOSX-386.s
@@ -0,0 +1,8 @@
+ .file "getcallerpc-MacOSX-386.s"
+
+ .text
+
+.globl _getcallerpc
+_getcallerpc:
+ movl 4(%ebp), %eax
+ ret
diff --git a/mkfiles/mkfile-Linux-arm b/mkfiles/mkfile-Linux-arm
new file mode 100644
index 00000000..709cb3fc
--- /dev/null
+++ b/mkfiles/mkfile-Linux-arm
@@ -0,0 +1,27 @@
+TARGMODEL= Posix
+TARGSHTYPE= sh
+CPUS= arm
+
+O= o
+OS= o
+
+AR= ar
+ARFLAGS= ruvs
+
+AS= arm-gcc -c
+ASFLAGS=
+
+CC= arm-gcc -c
+CFLAGS= -O\
+ -I$ROOT/Linux/arm/include\
+ -I$ROOT/include\
+ -DLINUX_ARM
+
+ANSICPP=
+LD= arm-gcc
+LDFLAGS=
+
+SYSLIBS=
+
+YACC= yacc
+YFLAGS= -d
diff --git a/mkfiles/mkfile-MacOSX-386 b/mkfiles/mkfile-MacOSX-386
new file mode 100644
index 00000000..5aab0869
--- /dev/null
+++ b/mkfiles/mkfile-MacOSX-386
@@ -0,0 +1,41 @@
+TARGMODEL= Posix
+TARGSHTYPE= sh
+CPUS= 386
+
+O= o
+OS= o
+
+AR= ar
+ARFLAGS= ruvs
+A= a
+
+AS= cc -c -arch i386
+ASFLAGS=
+
+ISYSROOT= -isysroot /Developer/SDKs/MacOSX10.4u.sdk
+
+CC= cc -c
+COPTFLAGS= -Os
+CDEBUGFLAGS=
+CTHREADFLAGS=
+CFLAGS= -arch i386\
+ -mmacosx-version-min=10.4\
+ -Wno-deprecated-declarations -Wuninitialized -Wunused -Wreturn-type -Wimplicit -Wno-four-char-constants -Wno-unknown-pragmas\
+ -pipe\
+ -fno-strict-aliasing\
+ -no-cpp-precomp\
+ -mno-fused-madd\
+ -I$ROOT/MacOSX/386/include\
+ -I$ROOT/include\
+ $COPTFLAGS $CDEBUGFLAGS\
+
+LD= cc -arch i386
+LDFLAGS=\
+ -mmacosx-version-min=10.4\
+ -multiply_defined suppress
+
+SYSLIBS=
+
+YACC= yacc
+YFLAGS= -d
+
diff --git a/module/csv.m b/module/csv.m
new file mode 100644
index 00000000..059d97ec
--- /dev/null
+++ b/module/csv.m
@@ -0,0 +1,8 @@
+CSV: module
+{
+ PATH: con "/dis/lib/csv.dis";
+
+ init: fn(b: Bufio);
+ getline: fn(fd: ref Bufio->Iobuf): list of string;
+ quote: fn(s: string): string;
+};
diff --git a/module/ida.m b/module/ida.m
new file mode 100644
index 00000000..f2503dd7
--- /dev/null
+++ b/module/ida.m
@@ -0,0 +1,24 @@
+Ida: module
+{
+ PATH: con "/dis/lib/ida/ida.dis";
+
+ Frag: adt {
+ dlen: int; # length of original data
+ m: int; # minimum pieces for reconstruction
+ a: array of int; # encoding array row for this fragment
+ enc: array of int; # encoded data
+
+ tag: array of byte; # user data, such as SHA1 hash
+ };
+
+ init: fn();
+ fragment: fn(data: array of byte, m: int): ref Frag;
+ consistent: fn(frags: array of ref Frag): array of ref Frag;
+ reconstruct: fn(frags: array of ref Frag): (array of byte, string);
+};
+
+Idatab: module
+{
+ PATH: con "/dis/lib/ida/idatab.dis";
+ init: fn(): array of int;
+};
diff --git a/module/rfc822.m b/module/rfc822.m
new file mode 100644
index 00000000..7e4c8897
--- /dev/null
+++ b/module/rfc822.m
@@ -0,0 +1,68 @@
+RFC822: module
+{
+ PATH: con "/dis/lib/rfc822.dis";
+
+ init: fn(b: Bufio);
+
+ # TO DO: multipart ...
+
+ # token values reserved to represent a word and a quoted string
+ Word, QString: con 1+iota;
+
+ Maxrequest: con 16*1024; # more than enough for anything sensible
+
+ Rfclex: adt {
+ fd: ref Bufio->Iobuf; # open on a single line
+ wordval: string; # text if Word or QString
+ tok: int; # last token seen
+ eof: int; # end of file (ignore subsequent ungetc)
+
+ seen: list of (int, string); # pushback
+
+ mk: fn(a: array of byte): ref Rfclex;
+ getc: fn(p: self ref Rfclex): int;
+ ungetc: fn(p: self ref Rfclex);
+ lex: fn(p: self ref Rfclex): int;
+ unlex: fn(p: self ref Rfclex);
+ skipws: fn(p: self ref Rfclex): int;
+
+ line: fn(p: self ref Rfclex): string;
+ };
+
+ readheaders: fn(fd: ref Bufio->Iobuf, limit: int): array of (string, array of byte);
+ parseparams: fn(ps: ref Rfclex): list of (string, string);
+ parsecontent: fn(ps: ref Rfclex, multipart: int, head: list of ref Content): list of ref Content;
+ mimefields: fn(ps: ref Rfclex): list of (string, list of (string, string));
+ # TO DO: parse addresses
+
+ quotable: fn(s: string): int;
+ quote: fn(s: string): string;
+
+ # convert an epoch time into http-formatted text
+ sec2date: fn(secs: int): string;
+
+ # convert a date in http text format to seconds from epoch
+ date2sec: fn(s: string): int;
+
+ # current time
+ now: fn(): int;
+
+ # current time as a string
+ time: fn(): string;
+
+ #
+ # mime-related things
+ #
+ Content: adt{
+ generic: string;
+ specific: string;
+ params: list of (string, string);
+
+ mk: fn(generic: string, specific: string, params: list of (string, string)): ref Content;
+ check: fn(c: self ref Content, oks: list of ref Content): int;
+ text: fn(c: self ref Content): string;
+ };
+
+ suffixclass: fn(name: string): (ref Content, ref Content);
+ dataclass: fn(a: array of byte): (ref Content, ref Content);
+};
diff --git a/module/uris.m b/module/uris.m
new file mode 100644
index 00000000..cc2226c6
--- /dev/null
+++ b/module/uris.m
@@ -0,0 +1,36 @@
+URIs: module
+{
+ PATH: con "/dis/lib/w3c/uris.dis";
+
+ # URI Generic Syntax (RFC 3986)
+ #
+ # scheme://authority/path?query#fragment
+ #
+ URI: adt
+ {
+ scheme: string;
+ userinfo: string; # authority, part I
+ host: string; # authority, part II
+ port: string; # authority, part III
+ path: string; # starts with / if path-abempty or path-absolute
+ query: string; # includes ? if not nil
+ fragment: string; # includes # if not nil
+
+ parse: fn(s: string): ref URI;
+ text: fn(u: self ref URI): string;
+ authority: fn(u: self ref URI): string;
+ addbase: fn(u: self ref URI, base: ref URI): ref URI;
+ copy: fn(u: self ref URI): ref URI;
+ hasauthority: fn(u: self ref URI): int;
+ isabsolute: fn(u: self ref URI): int;
+ nodots: fn(u: self ref URI): ref URI;
+ pathonly: fn(u: self ref URI): ref URI;
+ userpw: fn(u: self ref URI): (string, string); # ``deprecated format''
+ eq: fn(u: self ref URI, v: ref URI): int;
+ eqf: fn(u: self ref URI, v: ref URI): int;
+ };
+
+ init: fn();
+ dec: fn(s: string): string;
+ enc: fn(s: string, safe: string): string;
+};
diff --git a/utils/cvbit/cvbit.c b/utils/cvbit/cvbit.c
new file mode 100644
index 00000000..dcd140f0
--- /dev/null
+++ b/utils/cvbit/cvbit.c
@@ -0,0 +1,86 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <pool.h>
+
+static int invert = 0;
+
+void
+main(int argc, char **argv)
+{
+ Memimage *im, *om;
+ char *s;
+ ulong ofmt;
+
+ ofmt = 0;
+ ARGBEGIN{
+ case 'i':
+ invert = 1;
+ break;
+ case 'c':
+ s = ARGF();
+ if(s==nil)
+ break;
+ ofmt = strtochan(s);
+ if(ofmt == 0){
+ fprint(2, "cvbit: bad chan: %s\n", s);
+ exits("chan");
+ }
+ break;
+ }ARGEND
+
+ memimageinit();
+ im = readmemimage(0);
+ if(im == nil){
+ fprint(2, "cvbit: can't read image: %r\n");
+ exits("read");
+ }
+ if(ofmt){
+ om = allocmemimage(im->r, ofmt);
+ if(om == nil){
+ fprint(2, "cvbit: can't allocate new image: %r\n");
+ exits("alloc");
+ }
+ memimagedraw(om, om->r, im, im->r.min, nil, ZP, S);
+ }else
+ om = im;
+ if(invert){
+ uchar *buf;
+ int bpl, y, x;
+
+ bpl = bytesperline(om->r, om->depth);
+ buf = malloc(bpl);
+ for(y=om->r.min.y; y<om->r.max.y; y++){
+ if(unloadmemimage(om, Rpt(Pt(om->r.min.x,y), Pt(om->r.max.x,y+1)), buf, bpl) != bpl){
+ fprint(2, "cvbit: can't unload image line\n");
+ exits("unload");
+ }
+ for(x=0; x<bpl; x++)
+ buf[x] ^= 0xFF;
+ if(loadmemimage(om, Rpt(Pt(om->r.min.x,y), Pt(om->r.max.x,y+1)), buf, bpl) != bpl){
+ fprint(2, "cvbit: can't load image line\n");
+ exits("load");
+ }
+ }
+ }
+ if(writememimage(1, om) < 0){
+ fprint(2, "cvbit: can't write image: %r\n");
+ exits("write");
+ }
+ exits(nil);
+}
+
+char*
+poolname(Pool *p)
+{
+ USED(p);
+ return "none";
+}
+
+void
+poolsetcompact(Pool *p, void (*f)(void*, void*))
+{
+ USED(p);
+ USED(f);
+}
diff --git a/utils/cvbit/mkfile b/utils/cvbit/mkfile
new file mode 100644
index 00000000..8d4b853a
--- /dev/null
+++ b/utils/cvbit/mkfile
@@ -0,0 +1,15 @@
+<../../mkconfig
+
+TARG=cvbit
+
+OFILES= cvbit.$O\
+
+HFILES= \
+ $ROOT/include/draw.h\
+ $ROOT/include/memdraw.h\
+
+LIBS= draw memdraw draw bio 9
+
+BIN=$ROOT/$OBJDIR/bin
+
+<$ROOT/mkfiles/mkone-$SHELLTYPE