about summary refs log tree commit diff
diff options
context:
space:
mode:
authorBarton E. Schaefer <schaefer@zsh.org>2014-09-29 17:52:33 -0700
committerBarton E. Schaefer <schaefer@zsh.org>2014-09-29 17:52:33 -0700
commit32473f59a38631c4bb2403d835b9e007c8a0ddc7 (patch)
tree72df8959fcde37e5968a6c6301f8a98a9f860e5f
parent60e31a45b0c015d42e9dc8ca2c991930c3f7fd66 (diff)
parent8cb67e721b3f02779bf4cf0d6a368f67c49c11f8 (diff)
downloadzsh-32473f59a38631c4bb2403d835b9e007c8a0ddc7.tar.gz
zsh-32473f59a38631c4bb2403d835b9e007c8a0ddc7.tar.xz
zsh-32473f59a38631c4bb2403d835b9e007c8a0ddc7.zip
Merge branch 'master' of git://git.code.sf.net/p/zsh/code
Conflicts:
	ChangeLog
-rw-r--r--ChangeLog22
-rw-r--r--Functions/VCS_Info/VCS_INFO_reposub7
-rw-r--r--NEWS7
-rw-r--r--Src/exec.c92
-rw-r--r--Src/hashtable.c15
-rw-r--r--Src/hist.c7
-rw-r--r--Src/params.c39
-rw-r--r--Src/parse.c87
-rw-r--r--Src/signals.c2
-rw-r--r--Src/zsh.h4
-rw-r--r--Test/A04redirect.ztst54
11 files changed, 306 insertions, 30 deletions
diff --git a/ChangeLog b/ChangeLog
index 3cccf62ec..53850eb0a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,25 @@
+2014-09-29  Peter Stephenson  <p.w.stephenson@ntlworld.com>
+
+	* 33286: Src/exec.c, Test/A04redirect.ztst: handle redirections
+	for multiple named functions.
+
+	* 33285: NEWS, Src/exec.c, Src/hashtable.c, Src/parse.c,
+	Src/signals.c, Src/zsh.h, Test/A04redirect.ztst: redirections in
+	function definitions are applied at execution not definition.
+
+2014-09-29  Frank Terbeck  <ft@bewatermyfriend.org>
+
+	* 33277: Functions/VCS_Info/VCS_INFO_reposub: Fix
+	VCS_INFO_reposub's command expansion
+
+2014-09-29  Peter Stephenson  <p.stephenson@samsung.com>
+
+	* users/19183: Src/hist.c: handle unlikely error case with
+	fdopen() better.
+
+	* 33276: Src/params.c, Src/zsh.h: safer import of numerical
+	variables from environment.
+
 2014-09-28  Barton E. Schaefer  <schaefer@zsh.org>
 
 	* 33268: Src/exec.c, Src/init.c, Src/signals.c: interactive shells
diff --git a/Functions/VCS_Info/VCS_INFO_reposub b/Functions/VCS_Info/VCS_INFO_reposub
index 1c16f0e7b..8ebc90b6e 100644
--- a/Functions/VCS_Info/VCS_INFO_reposub
+++ b/Functions/VCS_Info/VCS_INFO_reposub
@@ -3,11 +3,12 @@
 ## Distributed under the same BSD-ish license as zsh itself.
 
 setopt localoptions extendedglob NO_shwordsplit
-local base=${1%%/##}
+local base=${1%%/##} tmp
 
-[[ $(pwd -P) == ${base}/* ]] || {
+tmp="$(pwd -P)"
+[[ $tmp == ${base}/* ]] || {
     printf '.'
     return 1
 }
-printf '%s' ${$(pwd -P)#$base/}
+printf '%s' ${tmp#$base/}
 return 0
diff --git a/NEWS b/NEWS
index 7dc134e68..bf8969b26 100644
--- a/NEWS
+++ b/NEWS
@@ -107,6 +107,13 @@ Changes since 5.0.0
   Also, the value of $pipestatus is now updated when a job stops, not just
   when it exits.
 
+- Redirections applied to function definitions take effect when the
+  function is executed, not when it is defined.  Other shells already
+  work this way.  For example,
+    fn() { echo hello } >~/logfile
+  Running fn writes "hello" to logfile.  In older versions of the shell
+  it would create an empty file at the point of definition.
+
 Changes between 4.2 and 5.0.0
 -----------------------------
 
diff --git a/Src/exec.c b/Src/exec.c
index fbd309580..10f71dab6 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -198,7 +198,8 @@ static char *blank_env[] = { NULL };
 /* Execution functions. */
 
 static int (*execfuncs[WC_COUNT-WC_CURSH]) _((Estate, int)) = {
-    execcursh, exectime, execfuncdef, execfor, execselect,
+    execcursh, exectime, NULL /* execfuncdef handled specially */,
+    execfor, execselect,
     execwhile, execrepeat, execcase, execif, execcond,
     execarith, execautofn, exectry
 };
@@ -1118,8 +1119,11 @@ execsimple(Estate state)
 	    fflush(xtrerr);
 	}
 	lv = (errflag ? errflag : cmdoutval);
-    } else
+    } else if (code == WC_FUNCDEF) {
+	lv = execfuncdef(state, NULL);
+    } else {
 	lv = (execfuncs[code - WC_CURSH])(state, 0);
+    }
 
     thisjob = otj;
 
@@ -2792,6 +2796,42 @@ execcmd(Estate state, int input, int output, int how, int last1)
 	return;
     }
 
+    if (type == WC_FUNCDEF) {
+	/*
+	 * The first word of a function definition is a list of
+	 * names.  If this is empty, we're doing an anonymous function:
+	 * in that case redirections are handled normally.
+	 * If not, it's a function definition: then we don't do
+	 * redirections here but pass in the list of redirections to
+	 * be stored for recall with the function.
+	 */
+	if (*state->pc != 0) {
+	    /* Nonymous, don't do redirections here */
+	    redir = NULL;
+	}
+    } else if (is_shfunc) {
+	Shfunc shf = (Shfunc)hn;
+	/*
+	 * A function definition may have a list of additional
+	 * redirections to apply, so retrieve it.
+	 */
+	if (shf->redir) {
+	    struct estate s;
+	    LinkList redir2;
+
+	    s.prog = shf->redir;
+	    s.pc = shf->redir->prog;
+	    s.strs = shf->redir->strs;
+	    redir2 = ecgetredirs(&s);
+	    if (!redir)
+		redir = redir2;
+	    else {
+		while (nonempty(redir2))
+		    addlinknode(redir, ugetnode(redir2));
+	    }
+	}
+    }
+
     if (type == WC_SIMPLE && !nullexec) {
 	char *s;
 	char trycd = (isset(AUTOCD) && isset(SHINSTDIN) &&
@@ -3240,7 +3280,33 @@ execcmd(Estate state, int input, int output, int how, int last1)
 		flags |= ESUB_REVERTPGRP;
 	    entersubsh(flags);
 	}
-	if (type >= WC_CURSH) {
+	if (type == WC_FUNCDEF) {
+	    Eprog redir_prog;
+	    if (!redir && wc_code(*beg) == WC_REDIR)  {
+		/*
+		 * We're not using a redirection from the currently
+		 * parsed environment, which is what we'd do for an
+		 * anonymous function, but there are redirections we
+		 * should store with the new function.
+		 */
+		struct estate s;
+
+		s.prog = state->prog;
+		s.pc = beg;
+		s.strs = state->prog->strs;
+
+		/*
+		 * The copy uses the wordcode parsing area, so save and
+		 * restore state.
+		 */
+		lexsave();
+		redir_prog = eccopyredirs(&s);
+		lexrestore();
+	    } else
+		redir_prog = NULL;
+	    
+	    lastval = execfuncdef(state, redir_prog);
+	} else if (type >= WC_CURSH) {
 	    if (last1 == 1)
 		do_exec = 1;
 	    lastval = (execfuncs[type - WC_CURSH])(state, do_exec);
@@ -4237,11 +4303,12 @@ exectime(Estate state, UNUSED(int do_exec))
 
 /**/
 static int
-execfuncdef(Estate state, UNUSED(int do_exec))
+execfuncdef(Estate state, Eprog redir_prog)
 {
     Shfunc shf;
     char *s = NULL;
     int signum, nprg, sbeg, nstrs, npats, len, plen, i, htok = 0, ret = 0;
+    int nfunc = 0;
     Wordcode beg = state->pc, end;
     Eprog prog;
     Patprog *pp;
@@ -4266,6 +4333,8 @@ execfuncdef(Estate state, UNUSED(int do_exec))
 	}
     }
 
+    DPUTS(!names && redir_prog,
+	  "Passing redirection to anon function definition.");
     while (!names || (s = (char *) ugetnode(names))) {
 	if (!names) {
 	    prog = (Eprog) zhalloc(sizeof(*prog));
@@ -4307,6 +4376,15 @@ execfuncdef(Estate state, UNUSED(int do_exec))
 	shf->node.flags = 0;
 	shf->filename = ztrdup(scriptfilename);
 	shf->lineno = lineno;
+	/*
+	 * redir_prog is permanently allocated --- but if
+	 * this function has multiple names we need an additional
+	 * one.
+	 */
+	if (nfunc++ && redir_prog)
+	    shf->redir = dupeprog(redir_prog, 0);
+	else
+	    shf->redir = redir_prog;
 	shfunc_set_sticky(shf);
 
 	if (!names) {
@@ -4337,6 +4415,8 @@ execfuncdef(Estate state, UNUSED(int do_exec))
 	    ret = lastval;
 
 	    freeeprog(shf->funcdef);
+	    if (shf->redir) /* shouldn't be */
+		freeeprog(shf->redir);
 	    zsfree(shf->filename);
 	    zfree(shf, sizeof(*shf));
 	    break;
@@ -4360,6 +4440,10 @@ execfuncdef(Estate state, UNUSED(int do_exec))
 	    shfunctab->addnode(shfunctab, ztrdup(s), shf);
 	}
     }
+    if (!nfunc && redir_prog) {
+	/* For completeness, shouldn't happen */
+	freeeprog(redir_prog);
+    }
     state->pc = end;
     return ret;
 }
diff --git a/Src/hashtable.c b/Src/hashtable.c
index ef187927b..7a430629d 100644
--- a/Src/hashtable.c
+++ b/Src/hashtable.c
@@ -887,6 +887,8 @@ freeshfuncnode(HashNode hn)
     zsfree(shf->node.nam);
     if (shf->funcdef)
 	freeeprog(shf->funcdef);
+    if (shf->redir)
+	freeeprog(shf->redir);
     zsfree(shf->filename);
     if (shf->sticky) {
 	if (shf->sticky->n_on_opts)
@@ -954,10 +956,19 @@ printshfuncnode(HashNode hn, int printflags)
 		printf(" \"$@\"");
 	    }
 	}   
-	printf("\n}\n");
+	printf("\n}");
     } else {
-	printf(" () { }\n");
+	printf(" () { }");
     }
+    if (f->redir) {
+	t = getpermtext(f->redir, NULL, 1);
+	if (t) {
+	    zputs(t, stdout);
+	    zsfree(t);
+	}
+    }
+
+    putchar('\n');
 }
 
 /**************************************/
diff --git a/Src/hist.c b/Src/hist.c
index d29a65afe..4660fd073 100644
--- a/Src/hist.c
+++ b/Src/hist.c
@@ -2593,7 +2593,12 @@ savehistfile(char *fn, int err, int writeflags)
 		out = NULL;
 	    } else {
 		int fd = open(tmpfile, O_CREAT | O_WRONLY | O_EXCL, 0600);
-		out = fd >= 0 ? fdopen(fd, "w") : NULL;
+		if (fd >=0) {
+		    out = fdopen(fd, "w");
+		    if (!out)
+			close(fd);
+		} else
+		    out = NULL;
 	    }
 
 #ifdef HAVE_FCHMOD
diff --git a/Src/params.c b/Src/params.c
index 0699ead85..61edc5d08 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -46,7 +46,7 @@
  
 /**/
 mod_export int locallevel;
- 
+
 /* Variables holding values of special parameters */
  
 /**/
@@ -325,9 +325,12 @@ IPDEF4("ZSH_SUBSHELL", &zsh_subshell),
 IPDEF5("COLUMNS", &zterm_columns, zlevar_gsu),
 IPDEF5("LINES", &zterm_lines, zlevar_gsu),
 IPDEF5U("ZLE_RPROMPT_INDENT", &rprompt_indent, zlevar_gsu),
-IPDEF5("OPTIND", &zoptind, varinteger_gsu),
 IPDEF5("SHLVL", &shlvl, varinteger_gsu),
-IPDEF5("TRY_BLOCK_ERROR", &try_errflag, varinteger_gsu),
+
+/* Don't import internal integer status variables. */
+#define IPDEF6(A,B,F) {{NULL,A,PM_INTEGER|PM_SPECIAL|PM_DONTIMPORT},BR((void *)B),GSU(F),10,0,NULL,NULL,NULL,0}
+IPDEF6("OPTIND", &zoptind, varinteger_gsu),
+IPDEF6("TRY_BLOCK_ERROR", &try_errflag, varinteger_gsu),
 
 #define IPDEF7(A,B) {{NULL,A,PM_SCALAR|PM_SPECIAL},BR((void *)B),GSU(varscalar_gsu),0,0,NULL,NULL,NULL,0}
 #define IPDEF7U(A,B) {{NULL,A,PM_SCALAR|PM_SPECIAL|PM_UNSET},BR((void *)B),GSU(varscalar_gsu),0,0,NULL,NULL,NULL,0}
@@ -742,7 +745,8 @@ createparamtable(void)
 	    if (!idigit(*iname) && isident(iname) && !strchr(iname, '[')) {
 		if ((!(pm = (Param) paramtab->getnode(paramtab, iname)) ||
 		     !(pm->node.flags & PM_DONTIMPORT || pm->node.flags & PM_EXPORTED)) &&
-		    (pm = setsparam(iname, metafy(ivalue, -1, META_DUP)))) {
+		    (pm = assignsparam(iname, metafy(ivalue, -1, META_DUP),
+				       ASSPM_ENV_IMPORT))) {
 		    pm->node.flags |= PM_EXPORTED;
 		    if (pm->node.flags & PM_SPECIAL)
 			pm->env = mkenvstr (pm->node.nam,
@@ -2271,6 +2275,13 @@ export_param(Param pm)
 mod_export void
 setstrvalue(Value v, char *val)
 {
+    assignstrvalue(v, val, 0);
+}
+
+/**/
+mod_export void
+assignstrvalue(Value v, char *val, int flags)
+{
     if (unset(EXECOPT))
 	return;
     if (v->pm->node.flags & PM_READONLY) {
@@ -2347,7 +2358,13 @@ setstrvalue(Value v, char *val)
 	break;
     case PM_INTEGER:
 	if (val) {
-	    v->pm->gsu.i->setfn(v->pm, mathevali(val));
+	    zlong ival;
+	    if (flags & ASSPM_ENV_IMPORT) {
+		char *ptr;
+		ival = zstrtol_underscore(val, &ptr, 0, 1);
+	    } else
+		ival = mathevali(val);
+	    v->pm->gsu.i->setfn(v->pm, ival);
 	    if ((v->pm->node.flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) &&
 		!v->pm->width)
 		v->pm->width = strlen(val);
@@ -2359,7 +2376,13 @@ setstrvalue(Value v, char *val)
     case PM_EFLOAT:
     case PM_FFLOAT:
 	if (val) {
-	    mnumber mn = matheval(val);
+	    mnumber mn;
+	    if (flags & ASSPM_ENV_IMPORT) {
+		char *ptr;
+		mn.type = MN_FLOAT;
+		mn.u.d = strtod(val, &ptr);
+	    } else
+		mn = matheval(val);
 	    v->pm->gsu.f->setfn(v->pm, (mn.type & MN_FLOAT) ? mn.u.d :
 			       (double)mn.u.l);
 	    if ((v->pm->node.flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) &&
@@ -2742,8 +2765,8 @@ assignsparam(char *s, char *val, int flags)
 	    }
 	}
     }
-    
-    setstrvalue(v, val);
+
+    assignstrvalue(v, val, flags);
     unqueue_signals();
     return v->pm;
 }
diff --git a/Src/parse.c b/Src/parse.c
index 3633417fe..6cba05082 100644
--- a/Src/parse.c
+++ b/Src/parse.c
@@ -375,6 +375,8 @@ init_parse(void)
 
 /* Build eprog. */
 
+/* careful: copy_ecstr is from arg1 to arg2, unlike memcpy */
+
 static void
 copy_ecstr(Eccstr s, char *p)
 {
@@ -386,24 +388,25 @@ copy_ecstr(Eccstr s, char *p)
 }
 
 static Eprog
-bld_eprog(void)
+bld_eprog(int heap)
 {
     Eprog ret;
     int l;
 
     ecadd(WCB_END());
 
-    ret = (Eprog) zhalloc(sizeof(*ret));
+    ret = heap ? (Eprog) zhalloc(sizeof(*ret)) : (Eprog) zalloc(sizeof(*ret));
     ret->len = ((ecnpats * sizeof(Patprog)) +
 		(ecused * sizeof(wordcode)) +
 		ecsoffs);
     ret->npats = ecnpats;
-    ret->nref = -1;		/* Eprog is on the heap */
-    ret->pats = (Patprog *) zhalloc(ret->len);
+    ret->nref = heap ? -1 : 1;
+    ret->pats = heap ? (Patprog *) zhalloc(ret->len) :
+	(Patprog *) zshcalloc(ret->len);
     ret->prog = (Wordcode) (ret->pats + ecnpats);
     ret->strs = (char *) (ret->prog + ecused);
     ret->shf = NULL;
-    ret->flags = EF_HEAP;
+    ret->flags = heap ? EF_HEAP : EF_REAL;
     ret->dump = NULL;
     for (l = 0; l < ecnpats; l++)
 	ret->pats[l] = dummy_patprog1;
@@ -455,7 +458,7 @@ parse_event(void)
         clear_hdocs();
         return NULL;
     }
-    return bld_eprog();
+    return bld_eprog(1);
 }
 
 /**/
@@ -534,7 +537,7 @@ parse_list(void)
 	yyerror(0);
 	return NULL;
     }
-    return bld_eprog();
+    return bld_eprog(1);
 }
 
 /*
@@ -553,7 +556,7 @@ parse_cond(void)
         clear_hdocs();
 	return NULL;
     }
-    return bld_eprog();
+    return bld_eprog(1);
 }
 
 /* This adds a list wordcode. The important bit about this is that it also
@@ -2554,6 +2557,73 @@ ecgetredirs(Estate s)
     return ret;
 }
 
+/*
+ * Copy the consecutive set of redirections in the state at s.
+ * Return NULL if none, else an Eprog consisting only of the
+ * redirections from permanently allocated memory.
+ *
+ * s is left in the state ready for whatever follows the redirections.
+ */
+
+/**/
+Eprog
+eccopyredirs(Estate s)
+{
+    Wordcode pc = s->pc;
+    wordcode code = *pc;
+    int ncode, ncodes = 0, r, type;
+
+    if (wc_code(code) != WC_REDIR)
+	return NULL;
+
+    init_parse();
+
+    while (wc_code(code) == WC_REDIR) {
+	type = WC_REDIR_TYPE(code);
+
+	DPUTS(type == REDIR_HEREDOC || type == REDIR_HEREDOCDASH,
+	      "unexpanded here document");
+
+	if (WC_REDIR_FROM_HEREDOC(code))
+	    ncode = 5;
+	else
+	    ncode = 3;
+	if (WC_REDIR_VARID(code))
+	    ncode++;
+	pc += ncode;
+	ncodes += ncode;
+	code = *pc;
+    }
+    r = ecused;
+    ecispace(r, ncodes);
+
+    code = *s->pc;
+    while (wc_code(code) == WC_REDIR) {
+	s->pc++;
+
+	ecbuf[r++] = code;
+	/* fd1 */
+	ecbuf[r++] = *s->pc++;
+	/* name or HERE string */
+	/* No DUP needed as we'll copy into Eprog immediately below */
+	ecbuf[r++] = ecstrcode(ecgetstr(s, EC_NODUP, NULL));
+	if (WC_REDIR_FROM_HEREDOC(code))
+	{
+	    /* terminator, raw */
+	    ecbuf[r++] = ecstrcode(ecgetstr(s, EC_NODUP, NULL));
+	    /* terminator, munged */
+	    ecbuf[r++] = ecstrcode(ecgetstr(s, EC_NODUP, NULL));
+	}
+	if (WC_REDIR_VARID(code))
+	    ecbuf[r++] = ecstrcode(ecgetstr(s, EC_NODUP, NULL));
+
+	code = *s->pc;
+    }
+
+    /* bld_eprog() appends a useful WC_END marker */
+    return bld_eprog(0);
+}
+
 /**/
 mod_export struct eprog dummy_eprog;
 
@@ -3546,4 +3616,3 @@ dump_autoload(char *nam, char *file, int on, Options ops, int func)
     }
     return ret;
 }
-
diff --git a/Src/signals.c b/Src/signals.c
index 84a2609e5..2df69f96e 100644
--- a/Src/signals.c
+++ b/Src/signals.c
@@ -763,7 +763,7 @@ dosavetrap(int sig, int level)
 	Shfunc shf, newshf = NULL;
 	if ((shf = (Shfunc)gettrapnode(sig, 1))) {
 	    /* Copy the node for saving */
-	    newshf = (Shfunc) zalloc(sizeof(*newshf));
+	    newshf = (Shfunc) zshcalloc(sizeof(*newshf));
 	    newshf->node.nam = ztrdup(shf->node.nam);
 	    newshf->node.flags = shf->node.flags;
 	    newshf->funcdef = dupeprog(shf->funcdef, 0);
diff --git a/Src/zsh.h b/Src/zsh.h
index fa7396112..d284c7aa7 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -1139,6 +1139,7 @@ struct shfunc {
     char *filename;             /* Name of file located in */
     zlong lineno;		/* line number in above file */
     Eprog funcdef;		/* function definition    */
+    Eprog redir;                /* redirections to apply */
     Emulation_options sticky;   /* sticky emulation definitions, if any */
 };
 
@@ -1820,7 +1821,8 @@ struct paramdef {
  */
 enum {
     ASSPM_AUGMENT = 1 << 0,
-    ASSPM_WARN_CREATE = 1 << 1
+    ASSPM_WARN_CREATE = 1 << 1,
+    ASSPM_ENV_IMPORT = 1 << 2
 };
 
 /* node for named directory hash table (nameddirtab) */
diff --git a/Test/A04redirect.ztst b/Test/A04redirect.ztst
index 7ad02db3b..6c38a3194 100644
--- a/Test/A04redirect.ztst
+++ b/Test/A04redirect.ztst
@@ -54,7 +54,7 @@
 0:Here-documents stripping tabs
 >barbar
 
-  cat <<-$'$HERE '`$(THERE) `'$((AND)) '"\EVERYWHERE"
+  cat <<-$'$HERE '`$(THERE) `'$((AND)) '"\EVERYWHERE" #
 # tabs again.  sorry about the max miller.
 	Here's a funny thing.  Here is a funny thing.
 	I went home last night.  There's a funny thing.
@@ -455,3 +455,55 @@
 
   [</dev/null ]
 1:check behaviour with square brackets
+
+  print any old rubbish >input1
+  () {
+    local var
+    read var
+    print I just read $var
+  } <input1 >output1
+  print Nothing output yet
+  cat output1
+0:anonymous function redirections are applied immediately
+>Nothing output yet
+>I just read any old rubbish
+
+  redirfn() {
+    local var
+    read var
+    print I want to tell you about $var
+    print Also, this might be an error >&2
+  } <input2 >output2 2>&1
+  print something I heard on the radio >input2
+  redirfn
+  print No output until after this
+  cat output2
+0:redirections with normal function definition
+>No output until after this
+>I want to tell you about something I heard on the radio
+>Also, this might be an error
+
+  which redirfn
+0:text output of function with redirections
+>redirfn () {
+>	local var
+>	read var
+>	print I want to tell you about $var
+>	print Also, this might be an error >&2
+>} < input2 > output2 2>&1
+
+  1func 2func 3func() { print Ich heisse $0 } >output3
+  for i in 1 2 3; do
+    f=${i}func
+    print Running $f
+    $f
+    cat output3
+    unfunction $f
+  done
+0:multiply named functions with redirection
+>Running 1func
+>Ich heisse 1func
+>Running 2func
+>Ich heisse 2func
+>Running 3func
+>Ich heisse 3func