Browse Source

Plan 9 from Bell Labs 2011-05-06

David du Colombier 13 years ago
parent
commit
7e115e5e87
7 changed files with 1623 additions and 4 deletions
  1. 4 4
      rc/bin/fshalt
  2. 16 0
      rc/bin/soelim
  3. 178 0
      sys/man/1/calls
  4. 39 0
      sys/man/1/seconds
  5. 41 0
      sys/man/1/soelim
  6. 873 0
      sys/src/cmd/calls.c
  7. 472 0
      sys/src/cmd/seconds.c

+ 4 - 4
rc/bin/fshalt

@@ -1,7 +1,7 @@
 #!/bin/rc
 # fshalt [-r] - sync (flush) and, if possible, halt all file servers
 #	and optionally reboot
-rfork ne
+rfork nes
 kerncopy=()
 reboot=no
 switch ($#*) {
@@ -32,8 +32,8 @@ fn isbootable {
 	test -e $1 && ~ `{file $1} *' plan 9 boot image'
 }
 fn usekernel {
-	kerncopy=/tmp/kern
-	cp $1 $kerncopy && echo -n using kernel $1...
+	kerncopy=kern
+	cp $1 /tmp/$kerncopy && echo -n using kernel $1...
 }
 
 # make a copy of the right kernel
@@ -129,7 +129,7 @@ fn x {
 	echo done halting
 
 	if (~ $reboot yes)
-		echo reboot kern >'#c/reboot'
+		echo reboot $kerncopy >'#c/reboot'
 }
 
 x

+ 16 - 0
rc/bin/soelim

@@ -0,0 +1,16 @@
+#!/bin/rc
+# joyless reimplementation of soelim
+# the $0 recursion is a bit ugly
+
+# canonicalise troff commands first with sed into ". so file" form.
+# but the space after the dot has to come out; tbl can't cope with it.
+# friggin' html macros can be longer than two characters; grrr.
+sed '/^[.'']/{
+	s/([^\\])\\".*$/\1/
+#	s/^(.)[	 ]*([^	 \\][^	 \\])[	 ]*/\1 \2 /
+	s/^(.)[	 ]*([^	 \\][^	 \\])/\1 \2/
+}' $* | awk '	BEGIN { me = "'$0'" }
+		$1 !~ /^[.'']$/	{ print; next }
+		$2 == "so" { system(me " " $3) ; next }
+		$2 == "nx" { system(me " " $3) ; exit }
+		{ print }' | sed 's/^([.'']) /\1/'

+ 178 - 0
sys/man/1/calls

@@ -0,0 +1,178 @@
+.TH CALLS 1
+.SH NAME
+calls \- print static call graph of a C program
+.SH SYNOPSIS
+.B calls
+[
+.B \-ptv
+] [
+.B \-f
+.I function
+] [
+.B \-w
+.I width
+\&...
+] [
+.B \-D
+.I def
+] [
+.B \-U
+.I def
+] [
+.B \-I
+.I dir
+] [
+file ...
+]
+.SH DESCRIPTION
+.I Calls
+reads
+.IR file s,
+which should be the source of C programs,
+and writes the analysed calling pattern to standard output.
+If no file names are given,
+standard input will be read.
+.I Calls
+is intended to help analyse the flow of a program by laying out the
+functions called in the hierarchical manner used in
+.I "Software Tools"
+by
+B. Kernighan and P. Plauger.
+.PP
+All input is first filtered through
+.IR cpp (1).
+Functions called but not defined within the source
+.IR file s
+are shown as:
+.PP
+.RS
+function
+.B [external]
+.RE
+.PP
+Recursive references are shown as:
+.PP
+.RS
+.B <<<
+function
+.RE
+.PP
+Options are:
+.TP 0.6i
+.B \-f
+Add
+.I function
+as a root of a call graph to be printed.
+.TP
+.B -p
+Make
+.I cpp
+search the APE
+.I include
+directories.
+.TP
+.B \-t
+Provides a terse form of output,
+in which the calling pattern for any
+function is printed only once on the first occurrence of the function.
+Subsequent occurrences output the function name and a notation
+.IP "" 1i
+\&...
+.BI "[see line " xx ]
+.IP "" 0.6i
+This is the default case.
+.TP 0.6i
+.B \-v
+Full output of function calling patterns on every occurrence.
+.TP
+.BI \-w n
+Set the output width to
+.IR n .
+The default is 80 columns.
+.TP
+.BI \-D name
+.PD 0
+.TP
+.PD 0.4v
+.BI \-D name=defn
+Define the
+.I name
+for
+.IR cpp ,
+as if by
+.BR #define .
+If no definition is given, the name is defined as 1.
+.TP
+.BI \-U name
+Remove any initial definition of
+.IR name ,
+where
+.I name
+is a reserved symbol that is predefined by
+.IR cpp .
+.TP
+.BI \-I dir
+Change the algorithm for searching for
+.B #include
+files whose names do not begin with
+.B /
+to look in
+.I dir
+before looking in the directories on the standard list.
+.SH EXAMPLES
+What does
+.I cat
+call?
+.IP
+.EX
+% calls -f main /sys/src/cmd/cat.c
+1   main
+2       cat
+3               read [external]
+4               write [external]
+5               sysfatal [external]
+6       open [external]
+7       sysfatal [external]
+8       close [external]
+9       exits [external]
+.EE
+.br
+.ne 7
+.PP
+What internal functions does
+.I dd
+call?
+.IP
+.EX
+% calls -f main /sys/src/cmd/dd.c | grep -v '\e[external\e]'
+1   main
+5       number
+6               <<< number
+9       match
+17      flsh
+21              term
+22                      stats
+25      term ... [see line 21]
+26      stats ... [see line 22]
+.EE
+.PP
+Note the recursion in
+.IR number .
+.PP
+Generate the PC kernel's internal call graph.
+.IP
+.EX
+cd /sys/src/9/pc
+calls -f main -I../port -I. ../port/*.c ../ip/*.c *.c |
+	grep -v external
+.EE
+.SH BUGS
+Forward declared functions defined within a function body which are not
+subsequently used within that function body will be listed as having been
+called.
+.PP
+Does not understand calls through function pointers.
+.PP
+Does not understand the restricted scope of
+.I static
+functions.

+ 39 - 0
sys/man/1/seconds

@@ -0,0 +1,39 @@
+.TH SECONDS 1 
+.SH NAME
+seconds \- convert human-readable date (and time) to seconds since epoch
+.SH SYNOPSIS
+.B seconds
+.I date
+\&...
+.SH DESCRIPTION
+.I Seconds
+prints the number of seconds since 1 Jan 1970
+corresponding to one or more human-readable
+.IR date s.
+Each
+.I date
+must be
+.I one
+argument;
+it may be necessary to enclose it in quotes.
+.PP
+.I Seconds
+accepts a somewhat wider range of input than just output from
+.IR date (1).
+The main requirement is that the date must be fully specified,
+with a day of month, month and year
+in any order.
+The month must be an English name (or abbreviation),
+not a number, and the year must contain 4 digits.
+Unambiguous time-zone names are understood (i.e., not
+.LR IST )
+or time zones may be written as
+.IR ±hhmm .
+Case is ignored.
+.SH SEE ALSO
+.IR date (1),
+.IR du (1),
+.IR ctime (2)
+.SH BUGS
+All-numeric dates, popular in the USA, are simply ambiguous,
+more so if the year is truncated to 2 digits.

+ 41 - 0
sys/man/1/soelim

@@ -0,0 +1,41 @@
+.TH SOELIM 1
+.\" .so in the NAME line confuses the ptx machinery; sorry
+.SH NAME
+soelim \- preprocess so inclusion commands in troff input 
+.SH SYNOPSIS
+.B soelim
+[
+.I files ...
+]
+.SH DESCRIPTION
+.I Soelim
+reads the specified files or the standard input and performs
+the textual inclusion implied by
+.IR troff (1)
+directives of the form
+.sp
+.ti +2m
+.B "\&.so some_file
+.sp
+when they appear at the beginning of input lines.  This is useful when
+using programs such as
+.IR tbl (1)
+that do not normally do this, allowing
+placement of individual tables or other text objects in separate files
+to be run as a part of a large document.
+.PP
+Note that inclusion can be suppressed by using
+.L '
+instead of
+.L .
+at the
+start of the line as in:
+.sp
+.ti +2m
+.B "\&'so /usr/share/lib/tmac/tmac.s
+.SH SOURCE
+.B /rc/bin/soelim
+.SH "SEE ALSO"
+.IR troff (1)
+.SH BUGS
+The shell script was written by Sape Mullender.

+ 873 - 0
sys/src/cmd/calls.c

@@ -0,0 +1,873 @@
+/*
+ * calls - print a paragraphed list of who calls whom within a body of C source
+ *
+ * Author: M.M. Taylor, DCIEM, Toronto, Canada.
+ * Modified by Alexis Kwan (HCR at DCIEM),
+ *	Kevin Szabo (watmath!wateng!ksbszabo, Elec Eng, U of Waterloo),
+ *	Tony Hansen, AT&T-IS, pegasus!hansen.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <bio.h>
+#include <String.h>
+
+#define CPP		"cpp -+"
+#define RINSTERR	((Rinst *)-1)	/* ugly; error but keep going */
+
+#define STREQ(a, b)	(*(a) == *(b) && strcmp(a, b) == 0)
+#define OTHER(rdwr)	((rdwr) == Rd? Wr: Rd)
+/* per 8c, all multibyte runes are considered alphabetic */
+#define ISIDENT(r) (isascii(r) && isalnum(r) || (r) == '_' || (r) >= Runeself)
+
+/* safe macros */
+#define newatom(in, atom)	(*(atom) = '\0', Bgetrune(in))
+#define checksys(atom)		strbsearch(atom, sysword, nelem(sysword))
+
+enum {
+	Printstats =	0,		/* flag */
+	Maxseen =	400,		/* # of instances w/in a function */
+	Maxdepth =	300,		/* max func call tree depth */
+	Hashsize =	2048,
+
+	Maxid =		256 + UTFmax,	/* max size of name */
+	Tabwidth =	8,
+	Maxwidth =	132,		/* limits tabbing */
+	Defwidth =	80,		/* limits tabbing */
+
+	Backslash =	'\\',
+	Quote =		'\'',
+
+	Rd =		0,		/* pipe indices */
+	Wr,
+
+	Stdin =		0,
+	Stdout,
+	Stderr,
+
+	Defn =		0,
+	Decl,
+	Call,
+
+	Nomore =	-1,
+	Added,
+	Found,
+};
+
+typedef struct Pushstate Pushstate;
+typedef struct Rinst Rinst;
+typedef struct Root Root;
+typedef struct Rname Rname;
+typedef struct Rnamehash Rnamehash;
+
+struct Pushstate {
+	int	kid;
+	int	fd;	/* original fd */
+	int	rfd;	/* replacement fd */
+	int	input;
+	int	open;
+};
+
+struct Rname {
+	Rinst	*dlistp;
+	int	rnameout;
+	char	rnamecalled;
+	char	rnamedefined;
+	char	*namer;
+	Rname	*next;		/* next in hash chain */
+};
+
+struct Rnamehash {
+	Rname	*head;
+};
+
+/* list of calling instances of those names */
+struct Rinst {
+	Rname	*namep;
+	Rinst	*calls;
+	Rinst	*calledby;
+};
+
+struct Root {
+	char	*func;
+	Root	*next;
+};
+
+char *aseen[Maxseen];		/* names being gathered within a function */
+Rname *namelist;		/* names being tracked */
+Rnamehash nameshash[Hashsize];
+Rname *activelist[Maxdepth];	/* names being output */
+String *cppopt;
+Root *roots;
+
+static struct stats {
+	long	highestseen;	/* aseen high water mark */
+	long	highestname;	/* namelist high water mark */
+	long	highestact;	/* activelist high water mark */
+	long	highgetfree;	/* getfrees high water mark */
+} stats;
+
+static long getfrees = 0;
+
+int bracket = 0;			/* curly brace count */
+int linect = 0;				/* line number */
+int activep = 0;			/* current function being output */
+
+/* options */
+int terse = 1;				/* track functions only once */
+int ntabs = (Maxwidth - 20) / Tabwidth;	/* how wide to go */
+
+char *dashes;				/* separators for deep nestings */
+
+/*
+ * These are C tokens after which a parenthesis is valid which would
+ * otherwise be tagged as function names.  The reserved words which are not
+ * listed are break, const, continue, default, goto and volatile.
+ */
+char *sysword[] = {
+	"auto", "case", "char", "do", "double", "else", "enum",
+	"extern", "float", "for", "if", "int", "long", "register",
+	"return", "short", "sizeof", "static", "struct", "switch",
+	"typedef", "union", "unsigned", "void", "while",
+};
+
+/*
+ * warning - print best error message possible
+ */
+void
+warning(char *s1, char *s2)
+{
+	fprint(2, "%s: ", argv0);
+	fprint(2, s1, s2);
+	fprint(2, "\n");
+}
+
+/*
+ *   safe malloc() code.  Does the checking for nil returns from malloc()
+ */
+void *
+emalloc(int size)
+{
+	void *f = mallocz(size, 1);
+
+	if (f == nil)
+		sysfatal("cannot allocate memory");
+	return f;
+}
+
+unsigned
+hash(char *s)
+{
+	unsigned h;
+	unsigned char *cp;
+
+	h = 0;
+	for(cp = (unsigned char *)s; *cp; h += *cp++)
+		h *= 1119;
+	return h;
+}
+
+/*
+ * lookup (name) accepts a pointer to a name and sees if the name is on the
+ * namelist.  If so, it returns a pointer to the nameblock.  Otherwise it
+ * returns nil.
+ */
+Rname *
+lookfor(char *name)
+{
+	int i;
+	unsigned buck;
+	Rname *np;
+	Rnamehash *hp;
+
+	buck = hash(name) % Hashsize;
+	hp = &nameshash[buck];
+	i = 0;
+	for (np = hp->head; np != nil; np = np->next, i++)
+		if (STREQ(name, np->namer))
+			return np;
+
+	if (i > stats.highestname)
+		stats.highestname = i;
+	return nil;
+}
+
+/*
+ * place() returns a pointer to the name on the namelist.  If the name was
+ * not in the namelist, place adds it.
+ */
+Rname *
+place(char name[])
+{
+	unsigned buck;
+	Rname *np;
+	Rnamehash *hp;
+
+	np = lookfor(name);
+	if (np != nil)
+		return np;
+
+	buck = hash(name) % Hashsize;
+	hp = &nameshash[buck];
+	np = emalloc(sizeof *np);
+	np->namer = strdup(name);
+	np->next = hp->head;
+	hp->head = np;
+	return np;
+}
+
+/*
+ * getfree returns a pointer to the next free instance block on the list
+ */
+Rinst *
+getfree(void)
+{
+	Rinst *ret, *new;
+	static Rinst *prev;
+
+	++getfrees;
+	if (getfrees > stats.highgetfree)
+		stats.highgetfree = getfrees;
+
+	if (prev == nil)
+		prev = emalloc(sizeof *prev);
+	new = emalloc(sizeof *new);
+
+	prev->calls = new;		/* also serves as next pointer */
+	new->calledby = prev;
+
+	ret = prev;
+	prev = new;
+	return ret;
+}
+
+/*
+ * install (np,rp) puts a new instance of a function into the linked list.
+ * It puts a pointer (np) to its own name (returned by place) into its
+ * namepointer, a pointer to the calling routine (rp) into its called-by
+ * pointer, and zero into the calls pointer.  It then puts a pointer to
+ * itself into the last function in the chain.
+ */
+Rinst *
+install(Rname *np, Rinst *rp)
+{
+	Rinst *newp;
+
+	if (np == nil)
+		return RINSTERR;
+	if ((newp = getfree()) == nil)
+		return nil;
+	newp->namep = np;
+	newp->calls = 0;
+	if (rp) {
+		while (rp->calls)
+			rp = rp->calls;
+		newp->calledby = rp->calledby;
+		rp->calls = newp;
+	} else {
+		newp->calledby = (Rinst *)np;
+		np->dlistp = newp;
+	}
+	return newp;
+}
+
+/*
+ * When scanning the text, each function instance is inserted into a
+ * linear list of names, using the Rname structure, when it is first
+ * encountered.  It is also inserted into the linked list using the Rinst
+ * structure.  The entry into the name list has a pointer to the defining
+ * instance in the linked list, and each entry in the linked list has a
+ * pointer back to the relevant name.  Newproc makes an entry in the
+ * defining list, which is distinguished from the called list only
+ * because it has no calledby link (value=0).  Add2proc enters into the
+ * called list, by inserting a link to the new instance in the calls
+ * pointer of the last entry (may be a defining instance, or a function
+ * called by that defining instance), and points back to the defining
+ * instance of the caller in its called-by pointer.
+ */
+Rinst *
+newproc(char *name)
+{
+	int i;
+	Rname *rp;
+
+	for (i = 0; i < Maxseen; i++)
+		if (aseen[i] != nil) {
+			free(aseen[i]);
+			aseen[i] = nil;
+		}
+	rp = place(name);
+	if (rp == nil)
+		return RINSTERR;
+	if (0 && rp->rnamedefined)
+		warning("function `%s' redefined", name);
+	rp->rnamedefined = 1;
+	return install(rp, nil);
+}
+
+/*
+ * add the function name to the calling stack of the current function.
+ */
+int
+add2call(char name[], Rinst *curp)
+{
+	Rname *p = place(name);
+	Rinst *ip = install(p, curp);
+
+	if (p != nil && curp != nil && curp->namep != nil &&
+	    !STREQ(p->namer, curp->namep->namer))
+		p->rnamecalled = 1;
+	return ip != nil;
+}
+
+/*
+ * backup removes an item from the active stack
+ */
+void
+backup(void)
+{
+	if (activep > 0)
+		activelist[--activep] = nil;
+}
+
+/*
+ * makeactive simply puts a pointer to the nameblock into a stack with
+ * maximum depth Maxdepth.  the error return only happens for stack
+ * overflow.
+ */
+int
+makeactive(Rname *func)
+{
+	if (activep < Maxdepth) {
+		if (activep > stats.highestact)
+			stats.highestact = activep;
+		activelist[activep++] = func;
+		return 1;
+	}
+	return 0;
+}
+
+/*
+ * active checks whether the pointer which is its argument has already
+ * occurred on the active list, and returns 1 if so.
+ */
+int
+active(Rname *func)
+{
+	int i;
+
+	for (i = 0; i < activep - 1; i++)
+		if (func == activelist[i])
+			return 1;
+	return 0;
+}
+
+/*
+ * output is a recursive routine to print one tab for each level of
+ * recursion, then the name of the function called, followed by the next
+ * function called by the same higher level routine.  In doing this, it
+ * calls itself to output the name of the first function called by the
+ * function whose name it is printing.  It maintains an active list of
+ * functions currently being printed by the different levels of
+ * recursion, and if it finds itself asked to print one which is already
+ * active, it terminates, marking that call with a '*'.
+ */
+void
+output(Rname *func, int tabc)
+{
+	int i, tabd, tabstar, tflag;
+	Rinst *nextp;
+
+	++linect;
+	print("\n%d", linect);
+	if (!makeactive(func)) {
+		print("   * nesting is too deep");
+		return;
+	}
+	tabstar = 0;
+	tabd = tabc;
+	for (; tabd > ntabs; tabstar++)
+		tabd -= ntabs;
+	if (tabstar > 0) {
+		print("  ");
+		for (i = 0; i < tabstar; i++)
+			print("<");
+	}
+	if (tabd == 0)
+		print("   ");
+	else
+		for (i = 0; i < tabd; i++)
+			print("\t");
+	if (active(func))
+		print("<<< %s", func->namer);		/* recursive call */
+	else if (func->dlistp == nil)
+		print("%s [external]", func->namer);
+	else {
+		print("%s", func->namer);
+		nextp = func->dlistp->calls;
+		if (!terse || !func->rnameout) {
+			++tabc;
+			if (!func->rnameout)
+				func->rnameout = linect;
+			if (tabc > ntabs && tabc%ntabs == 1 && nextp) {
+				print("\n%s", dashes);
+				tflag = 1;
+			} else
+				tflag = 0;
+			for (; nextp; nextp = nextp->calls)
+				output(nextp->namep, tabc);
+			if (tflag) {
+				print("\n%s", dashes);
+				tflag = 0;
+				USED(tflag);
+			}
+		} else if (nextp != nil)		/* not a leaf */
+			print(" ... [see line %d]",  func->rnameout);
+	}
+	backup();
+}
+
+/*
+ * Dumptree() lists out the calling stacks.  All names will be listed out
+ * unless some function names are specified in -f options.
+ */
+void
+dumptree(void)
+{
+	unsigned buck;
+	Root *rp;
+	Rname *np;
+
+	if (roots != nil)
+		for (rp = roots; rp != nil; rp = rp->next)
+			if ((np = lookfor(rp->func)) != nil) {
+				output(np, 0);
+				print("\n\n");
+			} else
+				fprint(2, "%s: function '%s' not found\n",
+					argv0, rp->func);
+	else
+		/* print everything */
+		for (buck = 0; buck < Hashsize; buck++)
+			for (np = nameshash[buck].head; np != nil; np = np->next)
+				if (!np->rnamecalled) {
+					output(np, 0);
+					print("\n\n");
+				}
+}
+
+/*
+ * Skipcomments() skips past any blanks and comments in the input stream.
+ */
+int
+skipcomments(Biobuf *in, int firstc)
+{
+	int c;
+
+	for (c = firstc; isascii(c) && isspace(c) || c == '/';
+	     c = Bgetrune(in)) {
+		if (c != '/')
+			continue;
+		c = Bgetrune(in);		/* read ahead */
+		if (c == Beof)
+			break;
+		if (c != '*' && c != '/') {	/* not comment start? */
+			Bungetrune(in);		/* push back readahead */
+			return '/';
+		}
+		if (c == '/') {			/* c++ style */
+			while ((c = Bgetrune(in)) != '\n' && c != Beof)
+				;
+			continue;
+		}
+		for (;;) {
+			/* skip to possible closing delimiter */
+			while ((c = Bgetrune(in)) != '*' && c != Beof)
+				;
+			if (c == Beof)
+				break;
+			/* else c == '*' */
+			c = Bgetrune(in);	 /* read ahead */
+			if (c == Beof || c == '/') /* comment end? */
+				break;
+			Bungetrune(in);	/* push back readahead */
+		}
+	}
+	return c;
+}
+
+/*
+ * isfndefn differentiates between an external declaration and a real
+ * function definition.  For instance, between:
+ *
+ *	extern char *getenv(char *), *strcmp(char *, char *);
+ *  and
+ *	char *getenv(char *name)
+ *	{}
+ *
+ * It does so by making the observation that nothing (except blanks and
+ * comments) can be between the right parenthesis and the semi-colon or
+ * comma following the extern declaration.
+ */
+int
+isfndefn(Biobuf *in)
+{
+	int c;
+
+	c = skipcomments(in, Bgetrune(in));
+	while (c != ')' && c != Beof)	/* consume arg. decl.s */
+		c = Bgetrune(in);
+	if (c == Beof)
+		return 1;		/* definition at Beof */
+	c = skipcomments(in, Bgetrune(in)); /* skip blanks between ) and ; */
+
+	if (c == ';' || c == ',')
+		return 0;		/* an extern declaration */
+	if (c != Beof)
+		Bungetrune(in);
+	return 1;			/* a definition */
+}
+
+/*
+ * Binary search -- from Knuth (6.2.1) Algorithm B.  Special case like this
+ * is WAY faster than the generic bsearch().
+ */
+int
+strbsearch(char *key, char **base, unsigned nel)
+{
+	int cmp;
+	char **last = base + nel - 1, **pos;
+
+	while (last >= base) {
+		pos = base + ((last - base) >> 1);
+		cmp = key[0] - (*pos)[0];
+		if (cmp == 0) {
+			/* there are no empty strings in the table */
+			cmp = strcmp(key, *pos);
+			if (cmp == 0)
+				return 1;
+		}
+		if (cmp < 0)
+			last = pos - 1;
+		else
+			base = pos + 1;
+	}
+	return 0;
+}
+
+/*
+ * see if we have seen this function within this process
+ */
+int
+seen(char *atom)
+{
+	int i;
+
+	for (i = 0; aseen[i] != nil && i < Maxseen-1; i++)
+		if (STREQ(atom, aseen[i]))
+			return Found;
+	if (i >= Maxseen-1)
+		return Nomore;
+	aseen[i] = strdup(atom);
+	if (i > stats.highestseen)
+		stats.highestseen = i;
+	return Added;
+}
+
+/*
+ * getfunc returns the name of a function in atom and Defn for a definition,
+ * Call for an internal call, or Beof.
+ */
+int
+getfunc(Biobuf *in, char *atom)
+{
+	int c, ss, quote;
+	char *ap, *ep = &atom[Maxid-1-UTFmax];
+	Rune r;
+
+	c = Bgetrune(in);
+	while (c != Beof) {
+		if (ISIDENT(c)) {
+			ap = atom;
+			do {
+				if (isascii(c))
+					*ap++ = c;
+				else {
+					r = c;
+					ap += runetochar(ap, &r);
+				}
+				c = Bgetrune(in);
+			} while(ap < ep && ISIDENT(c));
+			*ap = '\0';
+			if (ap >= ep) {	/* uncommon case: id won't fit */
+				/* consume remainder of too-long id */
+				while (ISIDENT(c))
+					c = Bgetrune(in);
+			}
+		}
+
+		switch (c) {
+		case Beof:
+			return Beof;
+		case '\t':		/* ignore white space */
+		case ' ':
+		case '\n':
+		case '\f':
+		case '\r':
+		case '/':		/* potential comment? */
+			c = skipcomments(in, Bgetrune(in));
+			break;
+		case Backslash:		/* consume a newline or something */
+		case ')':		/* end of parameter list */
+		default:
+			c = newatom(in, atom);
+			break;
+		case '#':	/* eat C compiler line control info */
+			/* CPP output will not span lines */
+			while ((c = Bgetrune(in)) != '\n' && c != Beof)
+				;
+			if (c == '\n')
+				c = Bgetrune(in);
+			break;
+		case Quote:		/* character constant */
+		case '\"':		/* string constant */
+			quote = c;
+			atom[0] = '\0';
+			while ((c = Bgetrune(in)) != quote && c != Beof)
+				if (c == Backslash)
+					Bgetrune(in);
+			if (c == quote)
+				c = Bgetrune(in);
+			break;
+		case '{':		/* start of a block */
+			bracket++;
+			c = newatom(in, atom);
+			break;
+		case '}':		/* end of a block */
+			--bracket;
+			if (bracket < 0)
+				fprint(2, "%s: bracket underflow!\n",
+					argv0);
+			c = newatom(in, atom);
+			break;
+		case '(':		/* parameter list for function? */
+			if (atom[0] != '\0' && !checksys(atom)) {
+				if (bracket == 0)
+					if (isfndefn(in))
+						return Defn;
+					else {
+						c = Bgetrune(in);
+						break;		/* ext. decl. */
+					}
+				ss = seen(atom);
+				if (ss == Nomore)
+					fprint(2, "%s: aseen overflow!\n", argv0);
+				if (bracket > 0 && ss == Added)
+					return Call;
+			}
+			c = newatom(in, atom);
+			break;
+		}
+	}
+	return Beof;
+}
+
+/*
+ * addfuncs() scans the input file for function names and adds them to the
+ * calling list.
+ */
+void
+addfuncs(int infd)
+{
+	int intern;
+	uintptr ok = 1;
+	char atom[Maxid];
+	Biobuf inbb;
+	Biobuf *in;
+	Rinst *curproc = nil;
+
+	in = &inbb;
+	Binit(in, infd, OREAD);
+	atom[0] = '\0';
+	while ((intern = getfunc(in, atom)) != Beof && ok)
+		if (intern == Call)
+			ok = add2call(atom, curproc);	/* function call */
+		else
+			ok = (uintptr)(curproc = newproc(atom)); /* fn def'n */
+	Bterm(in);
+}
+
+/*
+ * push a filter, cmd, onto fd.  if input, it's an input descriptor.
+ * returns a descriptor to replace fd, or -1 on error.
+ */
+static int
+push(int fd, char *cmd, int input, Pushstate *ps)
+{
+	int nfd, pifds[2];
+	String *s;
+
+	ps->open = 0;
+	ps->fd = fd;
+	ps->input = input;
+	if (fd < 0 || pipe(pifds) < 0)
+		return -1;
+	ps->kid = fork();
+	switch (ps->kid) {
+	case -1:
+		return -1;
+	case 0:
+		if (input)
+			dup(pifds[Wr], Stdout);
+		else
+			dup(pifds[Rd], Stdin);
+		close(pifds[input? Rd: Wr]);
+		dup(fd, (input? Stdin: Stdout));
+
+		s = s_new();
+		if (cmd[0] != '/')
+			s_append(s, "/bin/");
+		s_append(s, cmd);
+		execl(s_to_c(s), cmd, nil);
+		execl("/bin/rc", "rc", "-c", cmd, nil);
+		sysfatal("can't exec %s: %r", cmd);
+	default:
+		nfd = pifds[input? Rd: Wr];
+		close(pifds[input? Wr: Rd]);
+		break;
+	}
+	ps->rfd = nfd;
+	ps->open = 1;
+	return nfd;
+}
+
+static char *
+pushclose(Pushstate *ps)
+{
+	Waitmsg *wm;
+
+	if (ps->fd < 0 || ps->rfd < 0 || !ps->open)
+		return "not open";
+	close(ps->rfd);
+	ps->rfd = -1;
+	ps->open = 0;
+	while ((wm = wait()) != nil && wm->pid != ps->kid)
+		continue;
+	return wm? wm->msg: nil;
+}
+
+/*
+ * invoke the C preprocessor on the named files so that its
+ * output can be read.
+ *
+ * must fork/exec cpp for each input file.
+ * otherwise we get macro redefinitions and other problems.
+ */
+void
+scanfiles(int argc, char **argv)
+{
+	int i, infd;
+	char *sts;
+	Pushstate ps;
+	String *cmd;
+
+	cmd = s_new();
+	for (i = 0; i < argc; i++) {
+		s_reset(cmd);
+		s_append(cmd, s_to_c(cppopt));
+		s_append(cmd, " ");
+		s_append(cmd, argv[i]);
+
+		infd = push(Stdin, s_to_c(cmd), Rd, &ps);
+		if (infd < 0) {
+			warning("can't execute cmd `%s'", s_to_c(cmd));
+			return;
+		}
+
+		addfuncs(infd);
+
+		sts = pushclose(&ps);
+		if (sts != nil && sts[0] != '\0') {
+			warning("cmd `%s' failed", s_to_c(cmd));
+			fprint(2, "exit status %s\n", sts);
+		}
+	}
+	s_free(cmd);
+}
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s [-ptv] [-f func] [-w width] [-D define] [-U undef]"
+		" [-I dir] [file...]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	int i, width = Defwidth;
+	char _dashes[1024];
+	Root *rp;
+
+	cppopt = s_copy(CPP);
+	ARGBEGIN{
+	case 'f':			/* start from function arg. */
+		rp = emalloc(sizeof *rp);
+		rp->func = EARGF(usage());
+		rp->next = roots;
+		roots = rp;
+		break;
+	case 'p':			/* ape includes */
+		s_append(cppopt, " -I /sys/include/ape");
+		s_append(cppopt, " -I /");
+		s_append(cppopt, getenv("objtype"));
+		s_append(cppopt, "/include/ape");
+		break;
+	case 't':			/* terse (default) */
+		terse = 1;
+		break;
+	case 'v':
+		terse = 0;
+		break;
+	case 'w':			/* output width */
+		width = atoi(EARGF(usage()));
+		if (width <= 0)
+			width = Defwidth;
+		break;
+	case 'D':
+	case 'I':
+	case 'U':
+		s_append(cppopt, " -");
+		s_putc(cppopt, ARGC());
+		s_append(cppopt, EARGF(usage()));
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	/* initialize the dashed separator list for deep nesting */
+	ntabs = (width - 20) / Tabwidth;
+	for (i = 0; i < width && i+1 < sizeof dashes; i += 2) {
+		_dashes[i] = '-';
+		_dashes[i+1] = ' ';
+	}
+	if (i < sizeof dashes)
+		_dashes[i] = '\0';
+	else
+		_dashes[sizeof dashes - 1] = '\0';
+	dashes = _dashes;
+
+	scanfiles(argc, argv);
+	dumptree();
+
+	if (Printstats) {
+		fprint(2, "%ld/%d aseen entries\n", stats.highestseen, Maxseen);
+		fprint(2, "%ld longest namelist hash chain\n", stats.highestname);
+		fprint(2, "%ld/%d activelist high water mark\n",
+			stats.highestact, Maxdepth);
+		fprint(2, "%ld dlist high water mark\n", stats.highgetfree);
+	}
+	exits(0);
+}

+ 472 - 0
sys/src/cmd/seconds.c

@@ -0,0 +1,472 @@
+/*
+ * seconds absolute_date ... - convert absolute_date to seconds since epoch
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+
+typedef ulong Time;
+
+enum {
+	AM, PM, HR24,
+
+	/* token types */
+	Month = 1,
+	Year,
+	Day,
+	Timetok,
+	Tz,
+	Dtz,
+	Ignore,
+	Ampm,
+
+	Maxtok		= 6, /* only this many chars are stored in datetktbl */
+	Maxdateflds	= 25,
+};
+
+/*
+ * macros for squeezing values into low 7 bits of "value".
+ * all timezones we care about are divisible by 10, and the largest value
+ * (780) when divided is 78.
+ */
+#define TOVAL(tp, v)	((tp)->value = (v) / 10)
+#define FROMVAL(tp)	((tp)->value * 10)	/* uncompress */
+
+/* keep this struct small since we have an array of them */
+typedef struct {
+	char	token[Maxtok];
+	char	type;
+	schar	value;
+} Datetok;
+
+int dtok_numparsed;
+
+/* forwards */
+Datetok	*datetoktype(char *s, int *bigvalp);
+
+static Datetok datetktbl[];
+static unsigned szdatetktbl;
+
+/* parse 1- or 2-digit number, advance *cpp past it */
+static int
+eatnum(char **cpp)
+{
+	int c, x;
+	char *cp;
+
+	cp = *cpp;
+	c = *cp;
+	if (!isascii(c) || !isdigit(c))
+		return -1;
+	x = c - '0';
+
+	c = *++cp;
+	if (isascii(c) && isdigit(c)) {
+		x = 10*x + c - '0';
+		cp++;
+	}
+	*cpp = cp;
+	return x;
+}
+
+/* return -1 on failure */
+int
+parsetime(char *time, Tm *tm)
+{
+	tm->hour = eatnum(&time);
+	if (tm->hour == -1 || *time++ != ':')
+		return -1;			/* only hour; too short */
+
+	tm->min = eatnum(&time);
+	if (tm->min == -1)
+		return -1;
+	if (*time++ != ':') {
+		tm->sec = 0;
+		return 0;			/* no seconds; okay */
+	}
+
+	tm->sec = eatnum(&time);
+	if (tm->sec == -1)
+		return -1;
+
+	/* this may be considered too strict.  garbage at end of time? */
+	return *time == '\0' || isascii(*time) && isspace(*time)? 0: -1;
+}
+
+/*
+ * try to parse pre-split timestr in fields as an absolute date
+ */
+int
+tryabsdate(char **fields, int nf, Tm *now, Tm *tm)
+{
+	int i, mer = HR24, bigval = -1;
+	long flg = 0, ty;
+	char *p;
+	char upzone[32];
+	Datetok *tp;
+
+	now = localtime(time(0));	/* default to local time (zone) */
+	tm->tzoff = now->tzoff;
+	strncpy(tm->zone, now->zone, sizeof tm->zone);
+
+	tm->mday = tm->mon = tm->year = -1;	/* mandatory */
+	tm->hour = tm->min = tm->sec = 0;
+	dtok_numparsed = 0;
+
+	for (i = 0; i < nf; i++) {
+		if (fields[i][0] == '\0')
+			continue;
+		tp = datetoktype(fields[i], &bigval);
+		ty = (1L << tp->type) & ~(1L << Ignore);
+		if (flg & ty)
+			return -1;		/* repeated type */
+		flg |= ty;
+		switch (tp->type) {
+		case Year:
+			tm->year = bigval;
+			if (tm->year < 1970 || tm->year > 2106)
+				return -1;	/* can't represent in ulong */
+			/* convert 4-digit year to 1900 origin */
+			if (tm->year >= 1900)
+				tm->year -= 1900;
+			break;
+		case Day:
+			tm->mday = bigval;
+			break;
+		case Month:
+			tm->mon = tp->value - 1; /* convert to zero-origin */
+			break;
+		case Timetok:
+			if (parsetime(fields[i], tm) < 0)
+				return -1;
+			break;
+		case Dtz:
+		case Tz:
+			tm->tzoff = FROMVAL(tp);
+			/* tm2sec needs the name in upper case */
+			strcpy(upzone, fields[i]);
+			for (p = upzone; *p; p++)
+				if (isascii(*p) && islower(*p))
+					*p = toupper(*p);
+			strncpy(tm->zone, upzone, sizeof tm->zone);
+			break;
+		case Ignore:
+			break;
+		case Ampm:
+			mer = tp->value;
+			break;
+		default:
+			return -1;	/* bad token type: CANTHAPPEN */
+		}
+	}
+	if (tm->year == -1 || tm->mon == -1 || tm->mday == -1)
+		return -1;		/* missing component */
+	if (mer == PM)
+		tm->hour += 12;
+	return 0;
+}
+
+int
+prsabsdate(char *timestr, Tm *now, Tm *tm)
+{
+	int nf;
+	char *fields[Maxdateflds];
+	static char delims[] = "- \t\n/,";
+
+	nf = gettokens(timestr, fields, nelem(fields), delims+1);
+	if (nf > nelem(fields))
+		return -1;
+	if (tryabsdate(fields, nf, now, tm) < 0) {
+		char *p = timestr;
+
+		/*
+		 * could be a DEC-date; glue it all back together, split it
+		 * with dash as a delimiter and try again.  Yes, this is a
+		 * hack, but so are DEC-dates.
+		 */
+		while (--nf > 0) {
+			while (*p++ != '\0')
+				;
+			p[-1] = ' ';
+		}
+		nf = gettokens(timestr, fields, nelem(fields), delims);
+		if (nf > nelem(fields) || tryabsdate(fields, nf, now, tm) < 0)
+			return -1;
+	}
+	return 0;
+}
+
+int
+validtm(Tm *tm)
+{
+	if (tm->year < 0 || tm->mon < 0 || tm->mon > 11 ||
+	    tm->mday < 1 || tm->hour < 0 || tm->hour >= 24 ||
+	    tm->min < 0 || tm->min > 59 ||
+	    tm->sec < 0 || tm->sec > 61)	/* allow 2 leap seconds */
+		return 0;
+	return 1;
+}
+
+Time
+seconds(char *timestr)
+{
+	Tm date;
+
+	memset(&date, 0, sizeof date);
+	if (prsabsdate(timestr, localtime(time(0)), &date) < 0)
+		return -1;
+	return validtm(&date)? tm2sec(&date): -1;
+}
+
+int
+convert(char *timestr)
+{
+	char *copy;
+	Time tstime;
+
+	copy = strdup(timestr);
+	if (copy == nil)
+		sysfatal("out of memory");
+	tstime = seconds(copy);
+	free(copy);
+	if (tstime == -1) {
+		fprint(2, "%s: `%s' not a valid date\n", argv0, timestr);
+		return 1;
+	}
+	print("%lud\n", tstime);
+	return 0;
+}
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s date-time ...\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	int i, sts;
+
+	sts = 0;
+	ARGBEGIN{
+	default:
+		usage();
+	}ARGEND
+	if (argc == 0)
+		usage();
+	for (i = 0; i < argc; i++)
+		sts |= convert(argv[i]);
+	exits(sts != 0? "bad": 0);
+}
+
+/*
+ * Binary search -- from Knuth (6.2.1) Algorithm B.  Special case like this
+ * is WAY faster than the generic bsearch().
+ */
+Datetok *
+datebsearch(char *key, Datetok *base, unsigned nel)
+{
+	int cmp;
+	Datetok *last = base + nel - 1, *pos;
+
+	while (last >= base) {
+		pos = base + ((last - base) >> 1);
+		cmp = key[0] - pos->token[0];
+		if (cmp == 0) {
+			cmp = strncmp(key, pos->token, Maxtok);
+			if (cmp == 0)
+				return pos;
+		}
+		if (cmp < 0)
+			last = pos - 1;
+		else
+			base = pos + 1;
+	}
+	return 0;
+}
+
+Datetok *
+datetoktype(char *s, int *bigvalp)
+{
+	char *cp = s;
+	char c = *cp;
+	static Datetok t;
+	Datetok *tp = &t;
+
+	if (isascii(c) && isdigit(c)) {
+		int len = strlen(cp);
+
+		if (len > 3 && (cp[1] == ':' || cp[2] == ':'))
+			tp->type = Timetok;
+		else {
+			if (bigvalp != nil)
+				*bigvalp = atoi(cp); /* won't fit in tp->value */
+			if (len == 4)
+				tp->type = Year;
+			else if (++dtok_numparsed == 1)
+				tp->type = Day;
+			else
+				tp->type = Year;
+		}
+	} else if (c == '-' || c == '+') {
+		int val = atoi(cp + 1);
+		int hr =  val / 100;
+		int min = val % 100;
+
+		val = hr*60 + min;
+		TOVAL(tp, c == '-'? -val: val);
+		tp->type = Tz;
+	} else {
+		char lowtoken[Maxtok+1];
+		char *ltp = lowtoken, *endltp = lowtoken+Maxtok;
+
+		/* copy to lowtoken to avoid modifying s */
+		while ((c = *cp++) != '\0' && ltp < endltp)
+			*ltp++ = (isascii(c) && isupper(c)? tolower(c): c);
+		*ltp = '\0';
+		tp = datebsearch(lowtoken, datetktbl, szdatetktbl);
+		if (tp == nil) {
+			tp = &t;
+			tp->type = Ignore;
+		}
+	}
+	return tp;
+}
+
+
+/*
+ * to keep this table reasonably small, we divide the lexval for Tz and Dtz
+ * entries by 10 and truncate the text field at MAXTOKLEN characters.
+ * the text field is not guaranteed to be NUL-terminated.
+ */
+static Datetok datetktbl[] = {
+/*	text		token	lexval */
+	"acsst",	Dtz,	63,	/* Cent. Australia */
+	"acst",		Tz,	57,	/* Cent. Australia */
+	"adt",		Dtz,	-18,	/* Atlantic Daylight Time */
+	"aesst",	Dtz,	66,	/* E. Australia */
+	"aest",		Tz,	60,	/* Australia Eastern Std Time */
+	"ahst",		Tz,	60,	/* Alaska-Hawaii Std Time */
+	"am",		Ampm,	AM,
+	"apr",		Month,	4,
+	"april",	Month,	4,
+	"ast",		Tz,	-24,	/* Atlantic Std Time (Canada) */
+	"at",		Ignore,	0,	/* "at" (throwaway) */
+	"aug",		Month,	8,
+	"august",	Month,	8,
+	"awsst",	Dtz,	54,	/* W. Australia */
+	"awst",		Tz,	48,	/* W. Australia */
+	"bst",		Tz,	6,	/* British Summer Time */
+	"bt",		Tz,	18,	/* Baghdad Time */
+	"cadt",		Dtz,	63,	/* Central Australian DST */
+	"cast",		Tz,	57,	/* Central Australian ST */
+	"cat",		Tz,	-60,	/* Central Alaska Time */
+	"cct",		Tz,	48,	/* China Coast */
+	"cdt",		Dtz,	-30,	/* Central Daylight Time */
+	"cet",		Tz,	6,	/* Central European Time */
+	"cetdst",	Dtz,	12,	/* Central European Dayl.Time */
+	"cst",		Tz,	-36,	/* Central Standard Time */
+	"dec",		Month,	12,
+	"decemb",	Month,	12,
+	"dnt",		Tz,	6,	/* Dansk Normal Tid */
+	"dst",		Ignore,	0,
+	"east",		Tz,	-60,	/* East Australian Std Time */
+	"edt",		Dtz,	-24,	/* Eastern Daylight Time */
+	"eet",		Tz,	12,	/* East. Europe, USSR Zone 1 */
+	"eetdst",	Dtz,	18,	/* Eastern Europe */
+	"est",		Tz,	-30,	/* Eastern Standard Time */
+	"feb",		Month,	2,
+	"februa",	Month,	2,
+	"fri",		Ignore,	5,
+	"friday",	Ignore,	5,
+	"fst",		Tz,	6,	/* French Summer Time */
+	"fwt",		Dtz,	12,	/* French Winter Time  */
+	"gmt",		Tz,	0,	/* Greenwish Mean Time */
+	"gst",		Tz,	60,	/* Guam Std Time, USSR Zone 9 */
+	"hdt",		Dtz,	-54,	/* Hawaii/Alaska */
+	"hmt",		Dtz,	18,	/* Hellas ? ? */
+	"hst",		Tz,	-60,	/* Hawaii Std Time */
+	"idle",		Tz,	72,	/* Intl. Date Line, East */
+	"idlw",		Tz,	-72,	/* Intl. Date Line, West */
+	"ist",		Tz,	12,	/* Israel */
+	"it",		Tz,	22,	/* Iran Time */
+	"jan",		Month,	1,
+	"januar",	Month,	1,
+	"jst",		Tz,	54,	/* Japan Std Time,USSR Zone 8 */
+	"jt",		Tz,	45,	/* Java Time */
+	"jul",		Month,	7,
+	"july",		Month,	7,
+	"jun",		Month,	6,
+	"june",		Month,	6,
+	"kst",		Tz,	54,	/* Korea Standard Time */
+	"ligt",		Tz,	60,	/* From Melbourne, Australia */
+	"mar",		Month,	3,
+	"march",	Month,	3,
+	"may",		Month,	5,
+	"mdt",		Dtz,	-36,	/* Mountain Daylight Time */
+	"mest",		Dtz,	12,	/* Middle Europe Summer Time */
+	"met",		Tz,	6,	/* Middle Europe Time */
+	"metdst",	Dtz,	12,	/* Middle Europe Daylight Time*/
+	"mewt",		Tz,	6,	/* Middle Europe Winter Time */
+	"mez",		Tz,	6,	/* Middle Europe Zone */
+	"mon",		Ignore,	1,
+	"monday",	Ignore,	1,
+	"mst",		Tz,	-42,	/* Mountain Standard Time */
+	"mt",		Tz,	51,	/* Moluccas Time */
+	"ndt",		Dtz,	-15,	/* Nfld. Daylight Time */
+	"nft",		Tz,	-21,	/* Newfoundland Standard Time */
+	"nor",		Tz,	6,	/* Norway Standard Time */
+	"nov",		Month,	11,
+	"novemb",	Month,	11,
+	"nst",		Tz,	-21,	/* Nfld. Standard Time */
+	"nt",		Tz,	-66,	/* Nome Time */
+	"nzdt",		Dtz,	78,	/* New Zealand Daylight Time */
+	"nzst",		Tz,	72,	/* New Zealand Standard Time */
+	"nzt",		Tz,	72,	/* New Zealand Time */
+	"oct",		Month,	10,
+	"octobe",	Month,	10,
+	"on",		Ignore,	0,	/* "on" (throwaway) */
+	"pdt",		Dtz,	-42,	/* Pacific Daylight Time */
+	"pm",		Ampm,	PM,
+	"pst",		Tz,	-48,	/* Pacific Standard Time */
+	"sadt",		Dtz,	63,	/* S. Australian Dayl. Time */
+	"sast",		Tz,	57,	/* South Australian Std Time */
+	"sat",		Ignore,	6,
+	"saturd",	Ignore,	6,
+	"sep",		Month,	9,
+	"sept",		Month,	9,
+	"septem",	Month,	9,
+	"set",		Tz,	-6,	/* Seychelles Time ?? */
+	"sst",		Dtz,	12,	/* Swedish Summer Time */
+	"sun",		Ignore,	0,
+	"sunday",	Ignore,	0,
+	"swt",		Tz,	6,	/* Swedish Winter Time  */
+	"thu",		Ignore,	4,
+	"thur",		Ignore,	4,
+	"thurs",	Ignore,	4,
+	"thursd",	Ignore,	4,
+	"tue",		Ignore,	2,
+	"tues",		Ignore,	2,
+	"tuesda",	Ignore,	2,
+	"ut",		Tz,	0,
+	"utc",		Tz,	0,
+	"wadt",		Dtz,	48,	/* West Australian DST */
+	"wast",		Tz,	42,	/* West Australian Std Time */
+	"wat",		Tz,	-6,	/* West Africa Time */
+	"wdt",		Dtz,	54,	/* West Australian DST */
+	"wed",		Ignore,	3,
+	"wednes",	Ignore,	3,
+	"weds",		Ignore,	3,
+	"wet",		Tz,	0,	/* Western Europe */
+	"wetdst",	Dtz,	6,	/* Western Europe */
+	"wst",		Tz,	48,	/* West Australian Std Time */
+	"ydt",		Dtz,	-48,	/* Yukon Daylight Time */
+	"yst",		Tz,	-54,	/* Yukon Standard Time */
+	"zp4",		Tz,	-24,	/* GMT +4  hours. */
+	"zp5",		Tz,	-30,	/* GMT +5  hours. */
+	"zp6",		Tz,	-36,	/* GMT +6  hours. */
+};
+static unsigned szdatetktbl = nelem(datetktbl);