Zsh Mailing List Archive
Messages sorted by:
Reverse Date,
Date,
Thread,
Author
PATCH: allocating a new file descriptor
- X-seq: zsh-workers 21133
- From: Peter Stephenson <pws@xxxxxxx>
- To: zsh-workers@xxxxxxxxxx (Zsh hackers list)
- Subject: PATCH: allocating a new file descriptor
- Date: Tue, 12 Apr 2005 13:57:45 +0100
- Mailing-list: contact zsh-workers-help@xxxxxxxxxx; run by ezmlm
David Korn suggested it would be a good idea to have a syntax for the
shell to allocate a new file descriptor, rather than relying on picking
a number which (i) has to be under 10 (ii) may be in use by something
else. Oliver suggested a syntax which looks fairly clean. This
implements it in zsh. (It sounds like David already has something
working in ksh, too.)
It works basically like this:
integer myfd
# Allocate a new fd, storing the value in $myfd.
# The fd will be at least 10.
exec {myfd}>~/tmp/mylogfile
# This syntax already worked.
print This is a log message. >&$myfd
# Close the allocated fd.
exec {myfd}>&-
This gives us much better encapsulation for manipulating file
descriptors, something along the lines of files or file handles in other
languages.
The shell checks that the braces only contain valid characters for a
parameter ID, so the only likely clash with this syntax is for avid
users of the BRACE_CCL option where {myfd}>~/tmp/logfile would perform a
normal redirection of fd 1 and add the words d, f, m, y to the
command arguments. I don't think this widely used.
As it relies on braces it is turned off if the option IGNORE_BRACES is
set.
Index: Doc/Zsh/redirect.yo
===================================================================
RCS file: /cvsroot/zsh/zsh/Doc/Zsh/redirect.yo,v
retrieving revision 1.7
diff -u -r1.7 redirect.yo
--- Doc/Zsh/redirect.yo 28 Jun 2004 15:38:13 -0000 1.7
+++ Doc/Zsh/redirect.yo 12 Apr 2005 11:28:22 -0000
@@ -149,6 +149,31 @@
with the terminal (assuming file descriptor 1 had been)
and then file descriptor 1 would be associated with file var(fname).
+If instead of a digit one of the operators above is preceded by
+a valid identifier enclosed in braces, the shell will open a new
+file descriptor that is guaranteed to be at least 10 and set the
+parameter named by the identifier to the file descriptor opened.
+No whitespace is allowed between the closing brace and the redirection
+character. The option tt(IGNORE_BRACES) must not be set.
+For example:
+
+indent(... {myfd}>&1)
+
+This opens a new file descriptor that is a duplicate of file descriptor
+1 and sets the parameter tt(myfd) to the number of the file descriptor,
+which will be at least 10. The new file descriptor can be written to using
+the syntax tt(>&$myfd).
+
+The syntax tt({)var(varid)tt(}>&-), for example tt({myfd}>&-), may be used
+to close a file descriptor opened in this fashion. Note that the
+parameter given by var(varid) must previously be set to a file descriptor
+in this case.
+
+It is an error to open or close a file descriptor in this fashion when the
+parameter is readonly. However, it is not an error to read or write a file
+descriptor using tt(<&$)var(param) or tt(>&$)var(param) if var(param) is
+readonly.
+
The `tt(|&)' command separator described in
ifzman(em(Simple Commands & Pipelines) in zmanref(zshmisc))\
ifnzman(noderef(Simple Commands & Pipelines))
Index: Src/exec.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/exec.c,v
retrieving revision 1.86
diff -u -r1.86 exec.c
--- Src/exec.c 4 Apr 2005 09:35:43 -0000 1.86
+++ Src/exec.c 12 Apr 2005 11:28:22 -0000
@@ -1293,7 +1293,8 @@
wordcode code;
state->pc++;
- for (pc = state->pc; wc_code(code = *pc) == WC_REDIR; pc += 3);
+ for (pc = state->pc; wc_code(code = *pc) == WC_REDIR;
+ pc += WC_REDIR_WORDS(code));
mpipe(pipes);
@@ -1532,32 +1533,52 @@
}
}
-/* A multio is a list of fds associated with a certain fd. *
- * Thus if you do "foo >bar >ble", the multio for fd 1 will have *
- * two fds, the result of open("bar",...), and the result of *
- * open("ble",....). */
-
-/* Add a fd to an multio. fd1 must be < 10, and may be in any state. *
- * fd2 must be open, and is `consumed' by this function. Note that *
- * fd1 == fd2 is possible, and indicates that fd1 was really closed. *
- * We effectively do `fd2 = movefd(fd2)' at the beginning of this *
- * function, but in most cases we can avoid an extra dup by delaying *
- * the movefd: we only >need< to move it if we're actually doing a *
- * multiple redirection. */
+/*
+ * A multio is a list of fds associated with a certain fd.
+ * Thus if you do "foo >bar >ble", the multio for fd 1 will have
+ * two fds, the result of open("bar",...), and the result of
+ * open("ble",....).
+ */
+
+/*
+ * Add a fd to an multio. fd1 must be < 10, and may be in any state.
+ * fd2 must be open, and is `consumed' by this function. Note that
+ * fd1 == fd2 is possible, and indicates that fd1 was really closed.
+ * We effectively do `fd2 = movefd(fd2)' at the beginning of this
+ * function, but in most cases we can avoid an extra dup by delaying
+ * the movefd: we only >need< to move it if we're actually doing a
+ * multiple redirection.
+ *
+ * If varid is not NULL, we open an fd above 10 and set the parameter
+ * named varid to that value. fd1 is not used.
+ */
/**/
static void
-addfd(int forked, int *save, struct multio **mfds, int fd1, int fd2, int rflag)
+addfd(int forked, int *save, struct multio **mfds, int fd1, int fd2, int rflag,
+ char *varid)
{
int pipes[2];
- if (!mfds[fd1] || unset(MULTIOS)) {
+ if (varid) {
+ /* fd will be over 10, don't touch mfds */
+ fd1 = movefd(fd2);
+ fdtable[fd1] = FDT_EXTERNAL;
+ setiparam(varid, (zlong)fd1);
+ /*
+ * If setting the parameter failed, close the fd else
+ * it will leak.
+ */
+ if (errflag)
+ zclose(fd1);
+ } else if (!mfds[fd1] || unset(MULTIOS)) {
if(!mfds[fd1]) { /* starting a new multio */
mfds[fd1] = (struct multio *) zhalloc(sizeof(struct multio));
if (!forked && save[fd1] == -2)
save[fd1] = (fd1 == fd2) ? -1 : movefd(fd1);
}
- redup(fd2, fd1);
+ if (!varid)
+ redup(fd2, fd1);
mfds[fd1]->ct = 1;
mfds[fd1]->fds[0] = fd1;
mfds[fd1]->rflag = rflag;
@@ -2207,9 +2228,9 @@
/* Add pipeline input/output to mnodes */
if (input)
- addfd(forked, save, mfds, 0, input, 0);
+ addfd(forked, save, mfds, 0, input, 0, NULL);
if (output)
- addfd(forked, save, mfds, 1, output, 1);
+ addfd(forked, save, mfds, 1, output, 1, NULL);
/* Do process substitutions */
if (redir)
@@ -2226,14 +2247,14 @@
fixfds(save);
execerr();
}
- addfd(forked, save, mfds, fn->fd1, fn->fd2, 0);
+ addfd(forked, save, mfds, fn->fd1, fn->fd2, 0, fn->varid);
} else if (fn->type == REDIR_OUTPIPE) {
if (fn->fd2 == -1) {
closemnodes(mfds);
fixfds(save);
execerr();
}
- addfd(forked, save, mfds, fn->fd1, fn->fd2, 1);
+ addfd(forked, save, mfds, fn->fd1, fn->fd2, 1, fn->varid);
} else {
if (fn->type != REDIR_HERESTR && xpandredir(fn, redir))
continue;
@@ -2258,7 +2279,7 @@
zwarn("%e", NULL, errno);
execerr();
}
- addfd(forked, save, mfds, fn->fd1, fil, 0);
+ addfd(forked, save, mfds, fn->fd1, fil, 0, fn->varid);
break;
case REDIR_READ:
case REDIR_READWRITE:
@@ -2274,7 +2295,7 @@
zwarn("%e: %s", fn->name, errno);
execerr();
}
- addfd(forked, save, mfds, fn->fd1, fil, 0);
+ addfd(forked, save, mfds, fn->fd1, fil, 0, fn->varid);
/* If this is 'exec < file', read from stdin, *
* not terminal, unless `file' is a terminal. */
if (nullexec == 1 && fn->fd1 == 0 &&
@@ -2282,9 +2303,32 @@
init_io();
break;
case REDIR_CLOSE:
+ if (fn->varid) {
+ char *s = fn->varid;
+ struct value vbuf;
+ Value v;
+ int bad = 0;
+
+ if (!(v = getvalue(&vbuf, &s, 0))) {
+ bad = 1;
+ } else if (v->pm->flags & PM_READONLY) {
+ bad = 2;
+ } else {
+ fn->fd1 = (int)getintvalue(v);
+ bad = errflag;
+ }
+ if (bad) {
+ zwarn(bad == 2 ?
+ "can't close file descriptor from readonly parameter" :
+ "parameter %s does not contain a file descriptor",
+ fn->varid, 0);
+ execerr();
+ }
+ }
if (!forked && fn->fd1 < 10 && save[fn->fd1] == -2)
save[fn->fd1] = movefd(fn->fd1);
- closemn(mfds, fn->fd1);
+ if (fn->fd1 < 10)
+ closemn(mfds, fn->fd1);
zclose(fn->fd1);
break;
case REDIR_MERGEIN:
@@ -2292,7 +2336,8 @@
if (fn->fd2 < 10)
closemn(mfds, fn->fd2);
if (fn->fd2 > 9 &&
- (fdtable[fn->fd2] != FDT_UNUSED ||
+ ((fdtable[fn->fd2] != FDT_UNUSED &&
+ fdtable[fn->fd2] != FDT_EXTERNAL) ||
fn->fd2 == coprocin ||
fn->fd2 == coprocout)) {
fil = -1;
@@ -2313,7 +2358,8 @@
zwarn("%s: %e", fn->fd2 == -2 ? "coprocess" : fdstr, errno);
execerr();
}
- addfd(forked, save, mfds, fn->fd1, fil, fn->type == REDIR_MERGEOUT);
+ addfd(forked, save, mfds, fn->fd1, fil,
+ fn->type == REDIR_MERGEOUT, fn->varid);
break;
default:
if (IS_APPEND_REDIR(fn->type))
@@ -2336,11 +2382,14 @@
zwarn("%e: %s", fn->name, errno);
execerr();
}
- addfd(forked, save, mfds, fn->fd1, fil, 1);
+ addfd(forked, save, mfds, fn->fd1, fil, 1, fn->varid);
if(IS_ERROR_REDIR(fn->type))
- addfd(forked, save, mfds, 2, dfil, 1);
+ addfd(forked, save, mfds, 2, dfil, 1, NULL);
break;
}
+ if (errflag) {
+ execerr();
+ }
}
}
@@ -2845,6 +2894,7 @@
WC_SUBLIST_TYPE(pc[1]) == WC_SUBLIST_END &&
wc_code(pc[2]) == WC_PIPE && WC_PIPE_TYPE(pc[2]) == WC_PIPE_END &&
wc_code(pc[3]) == WC_REDIR && WC_REDIR_TYPE(pc[3]) == REDIR_READ &&
+ !WC_REDIR_VARID(pc[3]) &&
!pc[4] &&
wc_code(pc[6]) == WC_SIMPLE && !WC_SIMPLE_ARGC(pc[6])) {
/* $(< word) */
Index: Src/parse.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/parse.c,v
retrieving revision 1.49
diff -u -r1.49 parse.c
--- Src/parse.c 6 Feb 2005 20:36:42 -0000 1.49
+++ Src/parse.c 12 Apr 2005 11:28:23 -0000
@@ -111,6 +111,8 @@
* - must precede command-code (or WC_ASSIGN)
* - data contains type (<, >, ...)
* - followed by fd1 and name from struct redir
+ * - for the extended form {var}>... where the fd is assigned
+ * to var, there is an extra item to contain var
*
* WC_ASSIGN
* - data contains type (scalar, array) and number of array-elements
@@ -737,7 +739,8 @@
} else if (tok == BARAMP) {
int r;
- for (r = p + 1; wc_code(ecbuf[r]) == WC_REDIR; r += 3);
+ for (r = p + 1; wc_code(ecbuf[r]) == WC_REDIR;
+ r += WC_REDIR_WORDS(ecbuf[r]));
ecispace(r, 3);
ecbuf[r] = WCB_REDIR(REDIR_MERGEOUT);
@@ -779,8 +782,7 @@
if (IS_REDIROP(tok)) {
*complex = 1;
while (IS_REDIROP(tok)) {
- nr++;
- par_redir(&r);
+ nr += par_redir(&r, NULL);
}
}
switch (tok) {
@@ -871,10 +873,10 @@
if (!nr)
return 0;
} else {
- /* Three codes per redirection. */
+ /* Take account of redirections */
if (sr > 1) {
*complex = 1;
- r += (sr - 1) * 3;
+ r += sr - 1;
}
}
}
@@ -883,7 +885,7 @@
if (IS_REDIROP(tok)) {
*complex = 1;
while (IS_REDIROP(tok))
- par_redir(&r);
+ (void)par_redir(&r, NULL);
}
incmdpos = 1;
incasepat = 0;
@@ -1510,6 +1512,9 @@
* simple : { COMMAND | EXEC | NOGLOB | NOCORRECT | DASH }
{ STRING | ENVSTRING | ENVARRAY wordlist OUTPAR | redir }
[ INOUTPAR { SEPER } ( list1 | INBRACE list OUTBRACE ) ]
+ *
+ * Returns 0 if no code, else 1 plus the number of code words
+ * used up by redirections.
*/
/**/
@@ -1517,7 +1522,7 @@
par_simple(int *complex, int nr)
{
int oecused = ecused, isnull = 1, r, argc = 0, p, isfunc = 0, sr = 0;
- int c = *complex;
+ int c = *complex, nrediradd;
r = ecused;
for (;;) {
@@ -1576,16 +1581,55 @@
for (;;) {
if (tok == STRING) {
+ int redir_var = 0;
+
*complex = 1;
incmdpos = 0;
- ecstr(tokstr);
- argc++;
- yylex();
+
+ if (!isset(IGNOREBRACES) && *tokstr == Inbrace)
+ {
+ char *eptr = tokstr + strlen(tokstr) - 1;
+ char *ptr = eptr;
+
+ if (*ptr == Outbrace && ptr > tokstr + 1)
+ {
+ while (--ptr > tokstr)
+ if (!iident(*ptr))
+ break;
+ if (ptr == tokstr)
+ {
+ char *toksave = tokstr;
+ char *idstring = dupstrpfx(tokstr+1, eptr-tokstr-1);
+ redir_var = 1;
+ yylex();
+
+ if (IS_REDIROP(tok) && tokfd == -1)
+ {
+ *complex = c = 1;
+ nrediradd = par_redir(&r, idstring);
+ p += nrediradd;
+ sr += nrediradd;
+ }
+ else
+ {
+ ecstr(toksave);
+ argc++;
+ }
+ }
+ }
+ }
+
+ if (!redir_var)
+ {
+ ecstr(tokstr);
+ argc++;
+ yylex();
+ }
} else if (IS_REDIROP(tok)) {
*complex = c = 1;
- par_redir(&r);
- p += 3; /* 3 codes per redirection */
- sr++;
+ nrediradd = par_redir(&r, NULL);
+ p += nrediradd;
+ sr += nrediradd;
} else if (tok == INOUTPAR) {
int oldlineno = lineno, onp, so, oecssub = ecssub;
@@ -1670,6 +1714,8 @@
/*
* redir : ( OUTANG | ... | TRINANG ) STRING
+ *
+ * Return number of code words required for redirection
*/
static int redirtab[TRINANG - OUTANG + 1] = {
@@ -1691,10 +1737,10 @@
};
/**/
-static void
-par_redir(int *rp)
+static int
+par_redir(int *rp, char *idstring)
{
- int r = *rp, type, fd1, oldcmdpos, oldnc;
+ int r = *rp, type, fd1, oldcmdpos, oldnc, ncodes;
char *name;
oldcmdpos = incmdpos;
@@ -1706,7 +1752,7 @@
fd1 = tokfd;
yylex();
if (tok != STRING && tok != ENVSTRING)
- YYERRORV(ecused);
+ YYERROR(ecused);
incmdpos = oldcmdpos;
nocorrect = oldnc;
@@ -1721,23 +1767,35 @@
case REDIR_HEREDOCDASH: {
/* <<[-] name */
struct heredocs **hd;
+ int htype = type;
- /* If we ever need more than three codes (or less), we have to change
- * the factors in par_cmd() and par_simple(), too. */
- ecispace(r, 3);
- *rp = r + 3;
+ if (idstring)
+ {
+ type |= REDIR_VARID_MASK;
+ ncodes = 4;
+ }
+ else
+ ncodes = 3;
+
+ /* If we ever to change the number of codes, we have to change
+ * the definition of WC_REDIR_WORDS. */
+ ecispace(r, ncodes);
+ *rp = r + ncodes;
ecbuf[r] = WCB_REDIR(type);
ecbuf[r + 1] = fd1;
+ if (idstring)
+ ecbuf[r + 3] = ecstrcode(idstring);
+
for (hd = &hdocs; *hd; hd = &(*hd)->next);
*hd = zalloc(sizeof(struct heredocs));
(*hd)->next = NULL;
- (*hd)->type = type;
+ (*hd)->type = htype;
(*hd)->pc = r;
(*hd)->str = tokstr;
yylex();
- return;
+ return ncodes;
}
case REDIR_WRITE:
case REDIR_WRITENOW:
@@ -1745,14 +1803,14 @@
/* > >(...) */
type = REDIR_OUTPIPE;
else if (tokstr[0] == Inang && tokstr[1] == Inpar)
- YYERRORV(ecused);
+ YYERROR(ecused);
break;
case REDIR_READ:
if (tokstr[0] == Inang && tokstr[1] == Inpar)
/* < <(...) */
type = REDIR_INPIPE;
else if (tokstr[0] == Outang && tokstr[1] == Inpar)
- YYERRORV(ecused);
+ YYERROR(ecused);
break;
case REDIR_READWRITE:
if ((tokstr[0] == Inang || tokstr[0] == Outang) && tokstr[1] == Inpar)
@@ -1761,13 +1819,25 @@
}
yylex();
- /* If we ever need more than three codes (or less), we have to change
- * the factors in par_cmd() and par_simple(), too. */
- ecispace(r, 3);
- *rp = r + 3;
+ /* If we ever to change the number of codes, we have to change
+ * the definition of WC_REDIR_WORDS. */
+ if (idstring)
+ {
+ type |= REDIR_VARID_MASK;
+ ncodes = 4;
+ }
+ else
+ ncodes = 3;
+
+ ecispace(r, ncodes);
+ *rp = r + ncodes;
ecbuf[r] = WCB_REDIR(type);
ecbuf[r + 1] = fd1;
ecbuf[r + 2] = ecstrcode(name);
+ if (idstring)
+ ecbuf[r + 3] = ecstrcode(idstring);
+
+ return ncodes;
}
/**/
@@ -2316,6 +2386,10 @@
r->type = WC_REDIR_TYPE(code);
r->fd1 = *s->pc++;
r->name = ecgetstr(s, EC_DUP, NULL);
+ if (WC_REDIR_VARID(code))
+ r->varid = ecgetstr(s, EC_DUP, NULL);
+ else
+ r->varid = NULL;
addlinknode(ret, r);
Index: Src/text.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/text.c,v
retrieving revision 1.14
diff -u -r1.14 text.c
--- Src/text.c 22 Jun 2004 13:10:02 -0000 1.14
+++ Src/text.c 12 Apr 2005 11:28:23 -0000
@@ -789,10 +789,15 @@
case REDIR_MERGEOUT:
case REDIR_INPIPE:
case REDIR_OUTPIPE:
- if (f->fd1 != (IS_READFD(f->type) ? 0 : 1))
+ if (f->varid) {
+ taddchr('{');
+ taddstr(f->varid);
+ taddchr('}');
+ } else if (f->fd1 != (IS_READFD(f->type) ? 0 : 1))
taddchr('0' + f->fd1);
taddstr(fstr[f->type]);
- taddchr(' ');
+ if (f->type != REDIR_MERGEIN && f->type != REDIR_MERGEOUT)
+ taddchr(' ');
if (f->type == REDIR_HERESTR) {
if (has_token(f->name)) {
taddchr('\"');
Index: Src/zsh.h
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/zsh.h,v
retrieving revision 1.72
diff -u -r1.72 zsh.h
--- Src/zsh.h 31 Mar 2005 09:55:00 -0000 1.72
+++ Src/zsh.h 12 Apr 2005 11:28:23 -0000
@@ -246,6 +246,8 @@
REDIR_INPIPE, /* < <(...) */
REDIR_OUTPIPE /* > >(...) */
};
+#define REDIR_TYPE_MASK (0x1f)
+#define REDIR_VARID_MASK (0x20)
#define IS_WRITE_FILE(X) ((X)>=REDIR_WRITE && (X)<=REDIR_READWRITE)
#define IS_APPEND_REDIR(X) (IS_WRITE_FILE(X) && ((X) & 2))
@@ -267,9 +269,14 @@
*/
#define FDT_INTERNAL 1
/*
+ * Entry visible to other processes, for example created using
+ * the {varid}> file syntax.
+ */
+#define FDT_EXTERNAL 2
+/*
* Entry used by output from the XTRACE option.
*/
-#define FDT_XTRACE 2
+#define FDT_XTRACE 3
#ifdef PATH_DEV_FD
/*
* Entry used by a process substition.
@@ -277,7 +284,7 @@
* decremented on exit; we don't close entries greater than
* FDT_PROC_SUBST except when closing everything.
*/
-#define FDT_PROC_SUBST 3
+#define FDT_PROC_SUBST 4
#endif
/* Flags for input stack */
@@ -453,6 +460,7 @@
int type;
int fd1, fd2;
char *name;
+ char *varid;
};
/* The number of fds space is allocated for *
@@ -629,8 +637,11 @@
#define WC_PIPE_LINENO(C) (wc_data(C) >> 1)
#define WCB_PIPE(T,L) wc_bld(WC_PIPE, ((T) | ((L) << 1)))
-#define WC_REDIR_TYPE(C) wc_data(C)
+#define WC_REDIR_TYPE(C) (wc_data(C) & REDIR_TYPE_MASK)
+#define WC_REDIR_VARID(C) (wc_data(C) & REDIR_VARID_MASK)
#define WCB_REDIR(T) wc_bld(WC_REDIR, (T))
+/* Size of redir is 4 words if REDIR_VARID_MASK is set, else 3 */
+#define WC_REDIR_WORDS(C) (WC_REDIR_VARID(C) ? 4 : 3)
#define WC_ASSIGN_TYPE(C) (wc_data(C) & ((wordcode) 1))
#define WC_ASSIGN_TYPE2(C) ((wc_data(C) & ((wordcode) 2)) >> 1)
Index: Test/A04redirect.ztst
===================================================================
RCS file: /cvsroot/zsh/zsh/Test/A04redirect.ztst,v
retrieving revision 1.5
diff -u -r1.5 A04redirect.ztst
--- Test/A04redirect.ztst 30 Jun 2004 10:01:05 -0000 1.5
+++ Test/A04redirect.ztst 12 Apr 2005 11:28:23 -0000
@@ -235,3 +235,28 @@
0:null redir with NULLCMD=cat
<input
>input
+
+ exec {myfd}>logfile
+ print This is my logfile. >&$myfd
+ print Examining contents of logfile...
+ cat logfile
+0:Using {fdvar}> syntax to open a new file descriptor
+>Examining contents of logfile...
+>This is my logfile.
+
+ exec {myfd}>&-
+ print This message should disappear >&$myfd
+1q:Closing file descriptor using brace syntax
+?(eval):2: $myfd: bad file descriptor
+
+ typeset -r myfd
+ echo This should not appear {myfd}>nologfile
+1:Error opening file descriptor using readonly variable
+?(eval):2: read-only variable: myfd
+
+ typeset +r myfd
+ exec {myfd}>newlogfile
+ typeset -r myfd
+ exec {myfd}>&-
+1:Error closing file descriptor using readonly variable
+?(eval):4: can't close file descriptor from readonly parameter
--
Peter Stephenson <pws@xxxxxxx> Software Engineer
CSR PLC, Churchill House, Cambridge Business Park, Cowley Road
Cambridge, CB4 0WZ, UK Tel: +44 (0)1223 692070
**********************************************************************
This email and any files transmitted with it are confidential and
intended solely for the use of the individual or entity to whom they
are addressed. If you have received this email in error please notify
the system manager.
**********************************************************************
Messages sorted by:
Reverse Date,
Date,
Thread,
Author