about summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Stephenson <pws@users.sourceforge.net>2005-04-12 15:11:07 +0000
committerPeter Stephenson <pws@users.sourceforge.net>2005-04-12 15:11:07 +0000
commitb3f8e32e5cf5771eb5efb1e11c38dab377b14432 (patch)
treea0029fa908983c35956961f92cc51bda73487d93
parentbd718425bb41f08fb9d06968b339455abb37e2dd (diff)
downloadzsh-b3f8e32e5cf5771eb5efb1e11c38dab377b14432.tar.gz
zsh-b3f8e32e5cf5771eb5efb1e11c38dab377b14432.tar.xz
zsh-b3f8e32e5cf5771eb5efb1e11c38dab377b14432.zip
21133: New {myfd} syntax for allocating file descriptors
-rw-r--r--ChangeLog6
-rw-r--r--Doc/Zsh/redirect.yo25
-rw-r--r--Src/exec.c102
-rw-r--r--Src/parse.c132
-rw-r--r--Src/text.c9
-rw-r--r--Src/zsh.h17
-rw-r--r--Test/A04redirect.ztst25
7 files changed, 256 insertions, 60 deletions
diff --git a/ChangeLog b/ChangeLog
index ba385b60c..560902b01 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2005-04-12  Peter Stephenson  <pws@csr.com>
+
+	* 21133: Doc/Zsh/redirect.yo, Src/exec.c, Src/parse.c, Src/text.c,
+	Src/zsh.h, Test/A04redirect.ztst: New {myfd}> syntax for
+	allocating file descriptors.
+
 2005-04-11  Clint Adams  <clint@zsh.org>
 
 	* 21132: Completion/Unix/Type/_pdf, Completion/X/Command/_xpdf:
diff --git a/Doc/Zsh/redirect.yo b/Doc/Zsh/redirect.yo
index 167c3ef21..3ce4b4369 100644
--- a/Doc/Zsh/redirect.yo
+++ b/Doc/Zsh/redirect.yo
@@ -149,6 +149,31 @@ file descriptor 2 would be associated
 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))
diff --git a/Src/exec.c b/Src/exec.c
index d19c55ad4..e3555afd2 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -1293,7 +1293,8 @@ execpline2(Estate state, wordcode pcode,
 	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 @@ closeallelse(struct multio *mn)
 	}
 }
 
-/* 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",....).                                             */
+/*
+ * 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.                                              */
+/*
+ * 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 @@ execcmd(Estate state, int input, int output, int how, int last1)
 
     /* 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 @@ execcmd(Estate state, int input, int output, int how, int last1)
 		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 @@ execcmd(Estate state, int input, int output, int how, int last1)
 			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 @@ execcmd(Estate state, int input, int output, int how, int last1)
 			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 @@ execcmd(Estate state, int input, int output, int how, int last1)
 		    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 @@ execcmd(Estate state, int input, int output, int how, int last1)
 		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 @@ execcmd(Estate state, int input, int output, int how, int last1)
 		    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 @@ execcmd(Estate state, int input, int output, int how, int last1)
 			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 @@ getoutput(char *cmd, int qt)
 	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) */
diff --git a/Src/parse.c b/Src/parse.c
index 9a4a95bec..20649a6fb 100644
--- a/Src/parse.c
+++ b/Src/parse.c
@@ -111,6 +111,8 @@ struct heredocs *hdocs;
  *     - 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 @@ par_pline(int *complex)
     } 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 @@ par_cmd(int *complex)
     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 @@ par_cmd(int *complex)
 		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 @@ par_cmd(int *complex)
     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 @@ par_dinbrack(void)
  * 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 @@ static int
 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 @@ par_simple(int *complex, int nr)
 
     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 @@ par_simple(int *complex, int nr)
 
 /*
  * redir	: ( OUTANG | ... | TRINANG ) STRING
+ *
+ * Return number of code words required for redirection
  */
 
 static int redirtab[TRINANG - OUTANG + 1] = {
@@ -1691,10 +1737,10 @@ static int redirtab[TRINANG - OUTANG + 1] = {
 };
 
 /**/
-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 @@ par_redir(int *rp)
     fd1 = tokfd;
     yylex();
     if (tok != STRING && tok != ENVSTRING)
-	YYERRORV(ecused);
+	YYERROR(ecused);
     incmdpos = oldcmdpos;
     nocorrect = oldnc;
 
@@ -1721,23 +1767,35 @@ par_redir(int *rp)
     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 @@ par_redir(int *rp)
 	    /* > >(...) */
 	    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 @@ par_redir(int *rp)
     }
     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 @@ ecgetredirs(Estate s)
 	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);
 
diff --git a/Src/text.c b/Src/text.c
index f7d80ae73..ceb4bfdf4 100644
--- a/Src/text.c
+++ b/Src/text.c
@@ -789,10 +789,15 @@ getredirs(LinkList redirs)
 	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('\"');
diff --git a/Src/zsh.h b/Src/zsh.h
index b0ca94e09..6e1916690 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -246,6 +246,8 @@ enum {
     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 @@ enum {
  */
 #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 @@ enum {
  * 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 @@ struct redir {
     int type;
     int fd1, fd2;
     char *name;
+    char *varid;
 };
 
 /* The number of fds space is allocated for  *
@@ -629,8 +637,11 @@ struct eccstr {
 #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)
diff --git a/Test/A04redirect.ztst b/Test/A04redirect.ztst
index e4e8783f9..b85d6ecf5 100644
--- a/Test/A04redirect.ztst
+++ b/Test/A04redirect.ztst
@@ -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