about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog11
-rw-r--r--Doc/Zsh/builtins.yo4
-rw-r--r--Src/Modules/parameter.c2
-rw-r--r--Src/builtin.c50
-rw-r--r--Src/exec.c107
-rw-r--r--Src/hashtable.c9
-rw-r--r--Src/init.c44
-rw-r--r--Src/options.c18
-rw-r--r--Src/parse.c2
-rw-r--r--Src/signals.c5
-rw-r--r--Src/zsh.h30
-rw-r--r--Test/B07emulate.ztst46
12 files changed, 287 insertions, 41 deletions
diff --git a/ChangeLog b/ChangeLog
index 5a69f0174..4b1508a47 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2012-10-11  Peter Stephenson  <p.w.stephenson@ntlworld.com>
+
+	* 30726: Doc/Zsh/builtins.yo, Src/builtin.c, Src/exec.c,
+	Src/hashtable.c, Src/init.c, Src/options.c, Src/parse.c,
+	Src/signals.c, Src/zsh.h, Src/Modules/parameter.c,
+	Test/B07emulate.ztst: extend 30722 to handle the case
+	where shell options passed to the emulate command need
+	propagating to sticky emulation.
+
 2012-10-11  Peter Stephenson  <pws@csr.com>
 
 	* 30724: Src/exec.c, Src/jobs.c: shell code optimised to use
@@ -260,5 +269,5 @@
 
 *****************************************************
 * This is used by the shell to define $ZSH_PATCHLEVEL
-* $Revision: 1.5744 $
+* $Revision: 1.5745 $
 *****************************************************
diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo
index f7924a072..37319c9b0 100644
--- a/Doc/Zsh/builtins.yo
+++ b/Doc/Zsh/builtins.yo
@@ -462,6 +462,10 @@ sitem(4.)(The presence or absence of the tt(-R) switch to tt(emulate)
 corresponds to different sticky emulation modes, so for example
 `tt(emulate sh -c)', `tt(emulate -R sh -c)' and `tt(emulate csh -c)'
 are treated as three distinct sticky emulations.)
+sitem(5.)(Difference in shell options supplied in addition to the
+basic emulation also mean the sticky emulations are different, so for
+example `tt(emulate zsh -c)' and `tt(emulate zsh -o cbases -c)' are
+treated as distinct sticky emulations.)
 endsitem()
 )
 findex(enable)
diff --git a/Src/Modules/parameter.c b/Src/Modules/parameter.c
index f19dfce4f..a029c9cb4 100644
--- a/Src/Modules/parameter.c
+++ b/Src/Modules/parameter.c
@@ -289,7 +289,7 @@ setfunction(char *name, char *val, int dis)
     shf = (Shfunc) zshcalloc(sizeof(*shf));
     shf->funcdef = dupeprog(prog, 0);
     shf->node.flags = dis;
-    shf->emulation = sticky_emulation;
+    shfunc_set_sticky(shf);
 
     if (!strncmp(name, "TRAP", 4) &&
 	(sn = getsignum(name + 4)) != -1) {
diff --git a/Src/builtin.c b/Src/builtin.c
index 5cb643f23..8a83df711 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -2944,7 +2944,7 @@ bin_functions(char *name, char **argv, Options ops, int func)
 	    shf = (Shfunc) zshcalloc(sizeof *shf);
 	    shf->node.flags = on;
 	    shf->funcdef = mkautofn(shf);
-	    shf->emulation = sticky_emulation;
+	    shfunc_set_sticky(shf);
 	    shfunctab->addnode(shfunctab, ztrdup(*argv), shf);
 
 	    if (signum != -1) {
@@ -5007,11 +5007,15 @@ bin_emulate(UNUSED(char *nam), char **argv, Options ops, UNUSED(int func))
 {
     int opt_L = OPT_ISSET(ops, 'L');
     int opt_R = OPT_ISSET(ops, 'R');
-    int saveemulation, savesticky_emulation, savehackchar;
+    int saveemulation, savehackchar;
     int ret = 1, new_emulation;
-    char saveopts[OPT_SIZE], new_opts[OPT_SIZE], savesticky_opts[OPT_SIZE];
+    char saveopts[OPT_SIZE], new_opts[OPT_SIZE];
     char *cmd = 0;
     const char *shname = *argv;
+    LinkList optlist;
+    LinkNode optnode;
+    Emulation_options save_sticky;
+    OptIndex *on_ptr, *off_ptr;
 
     /* without arguments just print current emulation */
     if (!shname) {
@@ -5055,7 +5059,8 @@ bin_emulate(UNUSED(char *nam), char **argv, Options ops, UNUSED(int func))
     memcpy(new_opts, opts, sizeof(opts));
     savehackchar = keyboardhackchar;
     emulate(shname, OPT_ISSET(ops,'R'), &new_emulation, new_opts);
-    if (parseopts("emulate", &argv, new_opts, &cmd)) {
+    optlist = newlinklist();
+    if (parseopts("emulate", &argv, new_opts, &cmd, optlist)) {
 	ret = 1;
 	goto restore;
     }
@@ -5081,15 +5086,40 @@ bin_emulate(UNUSED(char *nam), char **argv, Options ops, UNUSED(int func))
     } else
 	return 0;
 
-    savesticky_emulation = sticky_emulation;
-    sticky_emulation = emulation;
-    memcpy(savesticky_opts, sticky_opts, sizeof(opts));
-    memcpy(sticky_opts, opts, sizeof(opts));
+    save_sticky = sticky;
+    sticky = hcalloc(sizeof(*sticky));
+    sticky->emulation = emulation;
+    for (optnode = firstnode(optlist); optnode; incnode(optnode)) {
+	/* Data is index into new_opts */
+	char *optptr = (char *)getdata(optnode);
+	if (*optptr)
+	    sticky->n_on_opts++;
+	else
+	    sticky->n_off_opts++;
+    }
+    if (sticky->n_on_opts)
+	on_ptr = sticky->on_opts =
+	    zhalloc(sticky->n_on_opts * sizeof(*sticky->on_opts));
+    else
+	on_ptr = NULL;
+    if (sticky->n_off_opts)
+	off_ptr = sticky->off_opts = zhalloc(sticky->n_off_opts *
+					     sizeof(*sticky->off_opts));
+    else
+	off_ptr = NULL;
+    for (optnode = firstnode(optlist); optnode; incnode(optnode)) {
+	/* Data is index into new_opts */
+	char *optptr = (char *)getdata(optnode);
+	int optno = optptr - new_opts;
+	if (*optptr)
+	    *on_ptr++ = optno;
+	else
+	    *off_ptr++ = optno;
+    }
     ret = eval(argv);
-    sticky_emulation = savesticky_emulation;
+    sticky = save_sticky;
     emulation = saveemulation;
     memcpy(opts, saveopts, sizeof(opts));
-    memcpy(sticky_opts, savesticky_opts, sizeof(opts));
 restore:
     keyboardhackchar = savehackchar;
     inittyptab();	/* restore banghist */
diff --git a/Src/exec.c b/Src/exec.c
index b2224cfb3..74b14d54d 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -4267,7 +4267,7 @@ execfuncdef(Estate state, UNUSED(int do_exec))
 	shf->node.flags = 0;
 	shf->filename = ztrdup(scriptfilename);
 	shf->lineno = lineno;
-	shf->emulation = sticky_emulation;
+	shfunc_set_sticky(shf);
 
 	if (!names) {
 	    /*
@@ -4319,6 +4319,46 @@ execfuncdef(Estate state, UNUSED(int do_exec))
     return ret;
 }
 
+/* Duplicate a sticky emulation */
+
+/**/
+
+mod_export Emulation_options
+sticky_emulation_dup(Emulation_options src, int useheap)
+{
+    Emulation_options newsticky = useheap ?
+	hcalloc(sizeof(*src)) : zshcalloc(sizeof(*src));
+    newsticky->emulation = src->emulation;
+    if (src->n_on_opts) {
+	size_t sz = src->n_on_opts * sizeof(*src->on_opts);
+	newsticky->n_on_opts = src->n_on_opts;
+	newsticky->on_opts = useheap ? zhalloc(sz) : zalloc(sz);
+	memcpy(newsticky->on_opts, src->on_opts, sz);
+    }
+    if (src->n_off_opts) {
+	size_t sz = src->n_off_opts * sizeof(*src->off_opts);
+	newsticky->n_off_opts = src->n_off_opts;
+	newsticky->off_opts = useheap ? zhalloc(sz) : zalloc(sz);
+	memcpy(newsticky->off_opts, src->off_opts, sz);
+    }
+
+    return newsticky;
+}
+
+/* Set the sticky emulation attributes for a shell function */
+
+/**/
+
+mod_export void
+shfunc_set_sticky(Shfunc shf)
+{
+    if (sticky)
+	shf->sticky = sticky_emulation_dup(sticky, 0);
+    else
+	shf->sticky = NULL;
+}
+
+
 /* Main entry point to execute a shell function. */
 
 /**/
@@ -4479,6 +4519,45 @@ loadautofn(Shfunc shf, int fksh, int autol)
 }
 
 /*
+ * Check if a sticky emulation differs from the current one.
+ */
+
+/**/
+
+int sticky_emulation_differs(Emulation_options sticky2)
+{
+    /* If no new sticky emulation, not a different emulation */
+    if (!sticky2)
+	return 0;
+    /* If no current sticky emulation, different */
+    if (!sticky)
+	return 1;
+    /* If basic emulation different, different */
+    if (sticky->emulation != sticky2->emulation)
+	return 1;
+    /* If differing numbers of options, different */
+    if (sticky->n_on_opts != sticky2->n_on_opts ||
+	sticky->n_off_opts != sticky2->n_off_opts)
+	return 1;
+    /*
+     * We need to compare option arrays, if non-null.
+     * We made parseopts() create the list of options in option
+     * order to make this easy.
+     */
+    /* If different options turned on, different */
+    if (sticky->n_on_opts &&
+	memcmp(sticky->on_opts, sticky2->on_opts,
+	       sticky->n_on_opts * sizeof(*sticky->on_opts)) != 0)
+	return 1;
+    /* If different options turned on, different */
+    if (sticky->n_off_opts &&
+	memcmp(sticky->off_opts, sticky2->off_opts,
+	       sticky->n_off_opts * sizeof(*sticky->off_opts)) != 0)
+	return 1;
+    return 0;
+}
+
+/*
  * execute a shell function
  *
  * name is the name of the function
@@ -4507,10 +4586,11 @@ doshfunc(Shfunc shfunc, LinkList doshargs, int noreturnval)
     char *name = shfunc->node.nam;
     int flags = shfunc->node.flags, ooflags;
     char *fname = dupstring(name);
-    int obreaks, saveemulation, savesticky_emulation, restore_sticky;
+    int obreaks, saveemulation, restore_sticky;
     Eprog prog;
     struct funcstack fstack;
     static int oflags;
+    Emulation_options save_sticky = NULL;
 #ifdef MAX_FUNCTION_DEPTH
     static int funcdepth;
 #endif
@@ -4548,9 +4628,9 @@ doshfunc(Shfunc shfunc, LinkList doshargs, int noreturnval)
      * function we need to restore the original options on exit.   */
     memcpy(saveopts, opts, sizeof(opts));
     saveemulation = emulation;
-    savesticky_emulation = sticky_emulation;
+    save_sticky = sticky;
 
-    if (shfunc->emulation && sticky_emulation != shfunc->emulation) {
+    if (sticky_emulation_differs(shfunc->sticky)) {
 	/*
 	 * Function is marked for sticky emulation.
 	 * Enable it now.
@@ -4563,9 +4643,24 @@ doshfunc(Shfunc shfunc, LinkList doshargs, int noreturnval)
 	 *
 	 * This propagates the sticky emulation to subfunctions.
 	 */
-	emulation = sticky_emulation = shfunc->emulation;
+	sticky = sticky_emulation_dup(shfunc->sticky, 1);
+	emulation = sticky->emulation;
 	restore_sticky = 1;
 	installemulation(emulation, opts);
+	if (sticky->n_on_opts) {
+	    OptIndex *onptr;
+	    for (onptr = sticky->on_opts;
+		 onptr < sticky->on_opts + sticky->n_on_opts;
+		 onptr++)
+		opts[*onptr] = 1;
+	}
+	if (sticky->n_off_opts) {
+	    OptIndex *offptr;
+	    for (offptr = sticky->off_opts;
+		 offptr < sticky->off_opts + sticky->n_off_opts;
+		 offptr++)
+		opts[*offptr] = 0;
+	}
     } else
 	restore_sticky = 0;
 
@@ -4674,7 +4769,7 @@ doshfunc(Shfunc shfunc, LinkList doshargs, int noreturnval)
 	 */
 	memcpy(opts, saveopts, sizeof(opts));
 	emulation = saveemulation;
-	sticky_emulation = savesticky_emulation;
+	sticky = save_sticky;
     } else if (isset(LOCALOPTIONS)) {
 	/* restore all shell options except PRIVILEGED and RESTRICTED */
 	saveopts[PRIVILEGED] = opts[PRIVILEGED];
diff --git a/Src/hashtable.c b/Src/hashtable.c
index b472e40b9..ef187927b 100644
--- a/Src/hashtable.c
+++ b/Src/hashtable.c
@@ -888,6 +888,15 @@ freeshfuncnode(HashNode hn)
     if (shf->funcdef)
 	freeeprog(shf->funcdef);
     zsfree(shf->filename);
+    if (shf->sticky) {
+	if (shf->sticky->n_on_opts)
+	    zfree(shf->sticky->on_opts,
+		  shf->sticky->n_on_opts * sizeof(*shf->sticky->on_opts));
+	if (shf->sticky->n_off_opts)
+	    zfree(shf->sticky->off_opts,
+		  shf->sticky->n_off_opts * sizeof(*shf->sticky->off_opts));
+	zfree(shf->sticky, sizeof(*shf->sticky));
+    }
     zfree(shf, sizeof(struct shfunc));
 }
 
diff --git a/Src/init.c b/Src/init.c
index 8f6c0ec6d..6c2ba13e4 100644
--- a/Src/init.c
+++ b/Src/init.c
@@ -246,7 +246,7 @@ parseargs(char **argv, char **runscript)
     opts[SHINSTDIN] = 0;
     opts[SINGLECOMMAND] = 0;
 
-    if (parseopts(NULL, &argv, opts, &cmd))
+    if (parseopts(NULL, &argv, opts, &cmd, NULL))
 	exit(1);
 
     paramlist = znewlinklist();
@@ -277,15 +277,37 @@ parseargs(char **argv, char **runscript)
     argzero = ztrdup(argzero);
 }
 
+/* Insert into list in order of pointer value */
+
+/**/
+static void
+parseopts_insert(LinkList optlist, void *ptr)
+{
+    LinkNode node;
+
+    for (node = firstnode(optlist); node; incnode(node)) {
+	if (ptr < getdata(node)) {
+	    insertlinknode(optlist, prevnode(node), ptr);
+	    return;
+	}
+    }
+
+    addlinknode(optlist, ptr);
+}
+
 /*
  * Parse shell options.
  * If nam is not NULL, this is called from a command; don't
  * exit on failure.
+ *
+ * If optlist is not NULL, it used to form a list of pointers
+ * into new_opts indicating which options have been changed.
  */
 
 /**/
 mod_export int
-parseopts(char *nam, char ***argvp, char *new_opts, char **cmdp)
+parseopts(char *nam, char ***argvp, char *new_opts, char **cmdp,
+	  LinkList optlist)
 {
     int optionbreak = 0;
     int action, optno;
@@ -364,8 +386,12 @@ parseopts(char *nam, char ***argvp, char *new_opts, char **cmdp)
 		    restricted = action;
 		} else if ((optno == EMACSMODE || optno == VIMODE) && nam) {
 		    WARN_OPTION("can't change option: %s", *argv);
-		} else if (dosetopt(optno, action, !nam, new_opts) && nam) {
-		    WARN_OPTION("can't change option: %s", *argv);
+		} else {
+		    if (dosetopt(optno, action, !nam, new_opts) && nam) {
+			WARN_OPTION("can't change option: %s", *argv);
+		    } else if (optlist) {
+			parseopts_insert(optlist, new_opts+optno);
+		    }
 		}
               break;
 	    } else if (isspace(STOUC(**argv))) {
@@ -385,8 +411,12 @@ parseopts(char *nam, char ***argvp, char *new_opts, char **cmdp)
 		    restricted = action;
 		} else if ((optno == EMACSMODE || optno == VIMODE) && nam) {
 		    WARN_OPTION("can't change option: %s", *argv);
-		} else if (dosetopt(optno, action, !nam, new_opts) && nam) {
-		    WARN_OPTION("can't change option: -%c", **argv);
+		} else {
+		    if (dosetopt(optno, action, !nam, new_opts) && nam) {
+			WARN_OPTION("can't change option: -%c", **argv);
+		    } else if (optlist) {
+			parseopts_insert(optlist, new_opts+optno);
+		    }
 		}
 	    }
 	}
@@ -396,7 +426,7 @@ parseopts(char *nam, char ***argvp, char *new_opts, char **cmdp)
     if (*cmdp) {
 	if (!*argv) {
 	    WARN_OPTION("string expected after -%s", *cmdp);
-	    exit(1);
+	    return 1;
 	}
 	*cmdp = *argv++;
     }
diff --git a/Src/options.c b/Src/options.c
index 87e9abe2d..80fef3d00 100644
--- a/Src/options.c
+++ b/Src/options.c
@@ -35,29 +35,21 @@
 /**/
 mod_export int emulation;
  
-/* current sticky emulation:  0 means none */
+/* current sticky emulation:  sticky = NULL means none */
 
 /**/
-mod_export int sticky_emulation;
+mod_export Emulation_options sticky;
 
 /* the options; e.g. if opts[SHGLOB] != 0, SH_GLOB is turned on */
- 
+
 /**/
 mod_export char opts[OPT_SIZE];
 
-/*
- * the options that need setting for current sticky emulation, if any:
- * same format as opts.
- */
-
-/**/
-mod_export char sticky_opts[OPT_SIZE];
- 
 /* Option name hash table */
 
 /**/
 mod_export HashTable optiontab;
- 
+
 /* The canonical option name table */
 
 #define OPT_CSH		EMULATE_CSH
@@ -786,7 +778,7 @@ dosetopt(int optno, int value, int force, char *new_opts)
 	    return -1;
 #endif /* GETPWNAM_FAKED */
     } else if ((optno == EMACSMODE || optno == VIMODE) && value) {
-	if (sticky_emulation)
+	if (sticky && sticky->emulation)
 	    return -1;
 	zleentry(ZLE_CMD_SET_KEYMAP, optno);
 	new_opts[(optno == EMACSMODE) ? VIMODE : EMACSMODE] = 0;
diff --git a/Src/parse.c b/Src/parse.c
index 8d2878cd7..096faa072 100644
--- a/Src/parse.c
+++ b/Src/parse.c
@@ -3482,7 +3482,7 @@ dump_autoload(char *nam, char *file, int on, Options ops, int func)
 	shf = (Shfunc) zshcalloc(sizeof *shf);
 	shf->node.flags = on;
 	shf->funcdef = mkautofn(shf);
-	shf->emulation = 0;
+	shf->sticky = NULL;
 	shfunctab->addnode(shfunctab, ztrdup(fdname(n) + fdhtail(n)), shf);
 	if (OPT_ISSET(ops,'X') && eval_autoload(shf, shf->node.nam, ops, func))
 	    ret = 1;
diff --git a/Src/signals.c b/Src/signals.c
index ad688094b..046ee6a4a 100644
--- a/Src/signals.c
+++ b/Src/signals.c
@@ -755,7 +755,10 @@ dosavetrap(int sig, int level)
 	    newshf->node.flags = shf->node.flags;
 	    newshf->funcdef = dupeprog(shf->funcdef, 0);
 	    newshf->filename = ztrdup(shf->filename);
-	    newshf->emulation = shf->emulation;
+	    if (shf->sticky) {
+		newshf->sticky = sticky_emulation_dup(shf->sticky, 0);
+	    } else
+		newshf->sticky = 0;
 	    if (shf->node.flags & PM_UNDEFINED)
 		newshf->funcdef->shf = newshf;
 	}
diff --git a/Src/zsh.h b/Src/zsh.h
index fee27ac10..e51572bcf 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -407,6 +407,7 @@ typedef struct cmdnam    *Cmdnam;
 typedef struct complist  *Complist;
 typedef struct conddef   *Conddef;
 typedef struct dirsav    *Dirsav;
+typedef struct emulation_options *Emulation_options;
 typedef struct features  *Features;
 typedef struct feature_enables  *Feature_enables;
 typedef struct funcstack *Funcstack;
@@ -1099,7 +1100,7 @@ struct shfunc {
     char *filename;             /* Name of file located in */
     zlong lineno;		/* line number in above file */
     Eprog funcdef;		/* function definition    */
-    int emulation;		/* sticky emulation for function */
+    Emulation_options sticky;   /* sticky emulation definitions, if any */
 };
 
 /* Shell function context types. */
@@ -2104,6 +2105,12 @@ enum {
     OPT_SIZE
 };
 
+/*
+ * Size required to fit an option number.
+ * If OPT_SIZE goes above 256 this will need to expand.
+ */
+typedef unsigned char OptIndex;
+
 #undef isset
 #define isset(X) (opts[X])
 #define unset(X) (!opts[X])
@@ -2112,6 +2119,27 @@ enum {
 #define jobbing  (isset(MONITOR))
 #define islogin  (isset(LOGINSHELL))
 
+/*
+ * Record of emulation and options that need to be set
+ * for a full "emulate".
+ */
+struct emulation_options {
+    /* The emulation itself */
+    int emulation;
+    /* The number of options in on_opts. */
+    int n_on_opts;
+    /* The number of options in off_opts. */
+    int n_off_opts;
+    /*
+     * Array of options to be turned on.
+     * Only options specified explicitly in the emulate command
+     * are recorded.  Null if n_on_opts is zero.
+     */
+    OptIndex *on_opts;
+    /* Array of options to be turned off, similar. */
+    OptIndex *off_opts;
+};
+
 /***********************************************/
 /* Definitions for terminal and display control */
 /***********************************************/
diff --git a/Test/B07emulate.ztst b/Test/B07emulate.ztst
index 569640bb4..315206a20 100644
--- a/Test/B07emulate.ztst
+++ b/Test/B07emulate.ztst
@@ -201,3 +201,49 @@
   emulate zsh -o fixallmybugs 'print This was executed, bad'
 1:emulate -c with incorrect options
 ?(eval):emulate:1: no such option: fixallmybugs
+
+  emulate zsh -c '
+    func() { [[ -o extendedglob ]] || print extendedglob is off }
+  '
+  func
+  emulate zsh -o extendedglob -c '
+    func() { [[ -o extendedglob ]] && print extendedglob is on }
+  '
+  func
+0:options specified alongside emulation are also sticky
+>extendedglob is off
+>extendedglob is on
+
+  emulate zsh -o extendedglob -c '
+    func_inner() { setopt nobareglobqual }
+  '
+  emulate zsh -o extendedglob -c '
+    func_outer() { 
+      func_inner
+      [[ -o bareglobqual ]] || print bareglobqual was turned off
+      [[ -o extendedglob ]] && print extendedglob is on, though
+    }
+  '
+  [[ -o extendedglob ]] || print extendedglob is initially off
+  func_outer
+0:options propagate between identical emulations
+>extendedglob is initially off
+>bareglobqual was turned off
+>extendedglob is on, though
+
+  emulate zsh -o extendedglob -c '
+    func_inner() { setopt nobareglobqual }
+  '
+  emulate zsh -o extendedglob -o cbases -c '
+    func_outer() { 
+      func_inner
+      [[ -o bareglobqual ]] && print bareglobqual is still on
+      [[ -o extendedglob ]] && print extendedglob is on, too
+    }
+  '
+  [[ -o extendedglob ]] || print extendedglob is initially off
+  func_outer
+0:options do not propagate between different emulations
+>extendedglob is initially off
+>bareglobqual is still on
+>extendedglob is on, too