--- a/sh.func.c
+++ b/sh.func.c
@@ -1096,6 +1096,11 @@ past:
 	stderror(ERR_NAME | ERR_NOTFOUND, "label");
 	break;
 
+    case TC_EXIT:
+	setname(short2str(Sgoal));
+	stderror(ERR_NAME | ERR_NOTFOUND, "return");
+	break;
+
     default:
 	break;
     }
@@ -2719,3 +2724,141 @@ getYN(const char *prompt)
 	continue;
     return doit;
 }
+
+int fpipe;
+Char *fdecl;
+
+void
+dofunction(Char **v, struct command *c)
+{
+    if (*++v == NULL) {
+	plist(&functions, VAR_READONLY);
+
+	return;
+    }
+    Sgoal = *v++;
+    Stype = TC_EXIT;
+    {
+	static int l;
+	int pv[2];
+	struct saved_state st;
+	struct varent *varp;
+	Char *p;
+
+	if (!letter(*(p = Sgoal)))
+	    stderror(ERR_NAME | ERR_FUNCBEGIN);
+	while (*++p)
+	    if (!alnum(*p))
+		stderror(ERR_NAME | ERR_FUNCALNUM);
+	if ((varp = adrof1(Sgoal, &functions))) {
+	    jmp_buf_t oldexit;
+	    int pvsav, ohaderr;
+	    Char *fsav;
+
+	    if (l == 16)
+		stderror(ERR_RECURSION);
+	    mypipe(pv);
+	    st_save(&st, pv[0], 0, NULL, v);
+	    pvsav = fpipe;
+	    fpipe = pv[1];
+	    fsav = fdecl;
+	    fdecl = *varp->vec;
+	    ohaderr = haderr;
+	    getexit(oldexit);
+	    l++;
+	    if (!setexit())
+		process(0);
+	    resexit(oldexit);
+	    haderr = ohaderr;
+	    st_restore(&st);
+	    xclose(pv[1]);
+	    fpipe = pvsav;
+	    fdecl = fsav;
+	    l--;
+
+	    return;
+	}
+	if (*v || c->t_dflg & (F_PIPEIN | F_PIPEOUT) ||
+	    c->t_dlef || c->t_drit || !isatty(OLDSTD))
+	    stderror(ERR_FUNC, Sgoal);
+	{
+	    Char funcexit[] = { 'r', 'e', 't', 'u', 'r', 'n', '\0' },
+		 *(*varvec)[2];
+	    struct Strbuf aword = Strbuf_INIT,
+			  func = Strbuf_INIT;
+	    struct wordent *histent = NULL,
+			   *ohistent = NULL;
+
+	    cleanup_push(&aword, Strbuf_cleanup);
+	    while (1) {
+		if (intty) {
+		    histent = xmalloc(sizeof(*histent));
+		    ohistent = xmalloc(sizeof(*histent));
+		    ohistent->word = STRNULL;
+		    ohistent->next = histent;
+		    histent->prev = ohistent;
+		}
+		if (intty && fseekp == feobp && aret == TCSH_F_SEEK)
+		    printprompt(1, bname);
+		(void) getword(&aword);
+		Strbuf_terminate(&aword);
+		if (intty && Strlen(aword.s) > 0) {
+		    histent->word = Strsave(aword.s);
+		    histent->next = xmalloc(sizeof(*histent));
+		    histent->next->prev = histent;
+		    histent = histent->next;
+		}
+
+		if (eq(aword.s, funcexit))
+		    break;
+		Strbuf_append(&func, aword.s);
+		Strbuf_append1(&func, ' ');
+		while (getword(&aword)) {
+		    Strbuf_terminate(&aword);
+		    if (intty && Strlen(aword.s) > 0) {
+			histent->word = Strsave(aword.s);
+			histent->next = xmalloc(sizeof(*histent));
+			histent->next->prev = histent;
+			histent = histent->next;
+		    }
+		    Strbuf_append(&func, aword.s);
+		    Strbuf_append1(&func, ' ');
+		}
+		func.s[func.len - 1] = '\n';
+
+		if (intty) {
+		    ohistent->prev = histgetword(histent);
+		    ohistent->prev->next = ohistent;
+		    savehist(ohistent, 0);
+		    freelex(ohistent);
+		    xfree(ohistent);
+		} else
+		    (void) getword(NULL);
+	    }
+
+	    if (intty) {
+		ohistent->prev = histgetword(histent);
+		ohistent->prev->next = ohistent;
+		savehist(ohistent, 0);
+		freelex(ohistent);
+		xfree(ohistent);
+	    }
+	    cleanup_until(&aword);
+	    if (!func.len)
+		return;
+	    func.s[--func.len] = 0;
+	    **(varvec = xmalloc(sizeof *varvec)) = func.s;
+	    *varvec[1] = NULL;
+	    setq(Sgoal, *varvec, &functions, VAR_READONLY);
+	}
+    }
+}
+
+void
+doreturn(Char **v, struct command *c)
+{
+    USE(c);
+    USE(v);
+
+    stderror(ERR_NAME | ERR_RETURN);
+}
