aboutsummaryrefslogtreecommitdiff
path: root/shell
diff options
context:
space:
mode:
authorRon Yorston <rmy@pobox.com>2017-08-02 11:33:53 +0100
committerRon Yorston <rmy@pobox.com>2017-08-02 11:33:53 +0100
commit8e20d4e9264a30dcc6d652943211668b1a6fe85b (patch)
tree7a7dcd4d55e2eb83374aed059a873486451c82e9 /shell
parentc8b51e5f274ff044ba2f9f350ead478d6c2063aa (diff)
parentec05df13b0f3bc69074909f078f981f417d95c89 (diff)
downloadbusybox-w32-8e20d4e9264a30dcc6d652943211668b1a6fe85b.tar.gz
busybox-w32-8e20d4e9264a30dcc6d652943211668b1a6fe85b.tar.bz2
busybox-w32-8e20d4e9264a30dcc6d652943211668b1a6fe85b.zip
Merge branch 'busybox' into merge
Diffstat (limited to 'shell')
-rw-r--r--shell/ash.c695
-rw-r--r--shell/ash_test/ash-heredoc/heredoc_empty.right3
-rwxr-xr-xshell/ash_test/ash-heredoc/heredoc_empty.tests8
-rw-r--r--shell/ash_test/ash-heredoc/heredoc_empty2.right4
-rwxr-xr-xshell/ash_test/ash-heredoc/heredoc_empty2.tests14
-rw-r--r--shell/ash_test/ash-misc/func_prio_over_builtins.right5
-rwxr-xr-xshell/ash_test/ash-misc/func_prio_over_builtins.tests5
-rw-r--r--shell/ash_test/ash-misc/func_return1.right (renamed from shell/ash_test/ash-misc/func6.right)0
-rwxr-xr-xshell/ash_test/ash-misc/func_return1.tests (renamed from shell/ash_test/ash-misc/func6.tests)0
-rw-r--r--shell/ash_test/ash-misc/func_return2.right (renamed from shell/hush_test/hush-misc/func6.right)0
-rwxr-xr-xshell/ash_test/ash-misc/func_return2.tests6
-rwxr-xr-xshell/ash_test/ash-redir/redir_script.tests9
-rw-r--r--shell/ash_test/ash-redir/redir_to_bad_fd255.right2
-rwxr-xr-xshell/ash_test/ash-redir/redir_to_bad_fd255.tests3
-rw-r--r--shell/ash_test/ash-redir/redir_to_bad_fd3.right2
-rwxr-xr-xshell/ash_test/ash-redir/redir_to_bad_fd3.tests3
-rw-r--r--shell/hush.c278
-rw-r--r--shell/hush_test/hush-heredoc/heredoc_empty2.right4
-rwxr-xr-xshell/hush_test/hush-heredoc/heredoc_empty2.tests14
-rw-r--r--shell/hush_test/hush-misc/func_prio_over_builtins.right5
-rwxr-xr-xshell/hush_test/hush-misc/func_prio_over_builtins.tests5
-rw-r--r--shell/hush_test/hush-misc/func_return1.right2
-rwxr-xr-xshell/hush_test/hush-misc/func_return1.tests (renamed from shell/hush_test/hush-misc/func6.tests)0
-rw-r--r--shell/hush_test/hush-misc/func_return2.right2
-rwxr-xr-xshell/hush_test/hush-misc/func_return2.tests6
-rwxr-xr-xshell/hush_test/hush-redir/redir_script.tests9
-rw-r--r--shell/hush_test/hush-redir/redir_to_bad_fd255.right1
-rwxr-xr-xshell/hush_test/hush-redir/redir_to_bad_fd255.tests3
-rw-r--r--shell/hush_test/hush-redir/redir_to_bad_fd3.right1
-rwxr-xr-xshell/hush_test/hush-redir/redir_to_bad_fd3.tests3
30 files changed, 708 insertions, 384 deletions
diff --git a/shell/ash.c b/shell/ash.c
index 62f750ca3..18e53a0da 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -409,7 +409,6 @@ struct globals_misc {
409#define EXERROR 1 /* a generic error */ 409#define EXERROR 1 /* a generic error */
410#define EXEXIT 4 /* exit the shell */ 410#define EXEXIT 4 /* exit the shell */
411 411
412 smallint isloginsh;
413 char nullstr[1]; /* zero length string */ 412 char nullstr[1]; /* zero length string */
414 413
415 char optlist[NOPTS]; 414 char optlist[NOPTS];
@@ -484,7 +483,6 @@ extern struct globals_misc *const ash_ptr_to_globals_misc;
484#define pending_int (G_misc.pending_int ) 483#define pending_int (G_misc.pending_int )
485#define got_sigchld (G_misc.got_sigchld ) 484#define got_sigchld (G_misc.got_sigchld )
486#define pending_sig (G_misc.pending_sig ) 485#define pending_sig (G_misc.pending_sig )
487#define isloginsh (G_misc.isloginsh )
488#define nullstr (G_misc.nullstr ) 486#define nullstr (G_misc.nullstr )
489#define optlist (G_misc.optlist ) 487#define optlist (G_misc.optlist )
490#define sigmode (G_misc.sigmode ) 488#define sigmode (G_misc.sigmode )
@@ -2117,7 +2115,7 @@ struct redirtab;
2117struct globals_var { 2115struct globals_var {
2118 struct shparam shellparam; /* $@ current positional parameters */ 2116 struct shparam shellparam; /* $@ current positional parameters */
2119 struct redirtab *redirlist; 2117 struct redirtab *redirlist;
2120 int preverrout_fd; /* save fd2 before print debug if xflag is set. */ 2118 int preverrout_fd; /* stderr fd: usually 2, unless redirect moved it */
2121 struct var *vartab[VTABSIZE]; 2119 struct var *vartab[VTABSIZE];
2122 struct var varinit[ARRAY_SIZE(varinit_data)]; 2120 struct var varinit[ARRAY_SIZE(varinit_data)];
2123}; 2121};
@@ -2596,8 +2594,20 @@ putprompt(const char *s)
2596#endif 2594#endif
2597 2595
2598/* expandstr() needs parsing machinery, so it is far away ahead... */ 2596/* expandstr() needs parsing machinery, so it is far away ahead... */
2599static const char *expandstr(const char *ps); 2597static const char *expandstr(const char *ps, int syntax_type);
2598/* Values for syntax param */
2599#define BASESYNTAX 0 /* not in quotes */
2600#define DQSYNTAX 1 /* in double quotes */
2601#define SQSYNTAX 2 /* in single quotes */
2602#define ARISYNTAX 3 /* in arithmetic */
2603#if ENABLE_ASH_EXPAND_PRMT
2604# define PSSYNTAX 4 /* prompt. never passed to SIT() */
2605#endif
2606/* PSSYNTAX expansion is identical to DQSYNTAX, except keeping '\$' as '\$' */
2600 2607
2608/*
2609 * called by editline -- any expansions to the prompt should be added here.
2610 */
2601static void 2611static void
2602setprompt_if(smallint do_set, int whichprompt) 2612setprompt_if(smallint do_set, int whichprompt)
2603{ 2613{
@@ -2621,7 +2631,7 @@ setprompt_if(smallint do_set, int whichprompt)
2621 } 2631 }
2622#if ENABLE_ASH_EXPAND_PRMT 2632#if ENABLE_ASH_EXPAND_PRMT
2623 pushstackmark(&smark, stackblocksize()); 2633 pushstackmark(&smark, stackblocksize());
2624 putprompt(expandstr(prompt)); 2634 putprompt(expandstr(prompt, PSSYNTAX));
2625 popstackmark(&smark); 2635 popstackmark(&smark);
2626#else 2636#else
2627 putprompt(prompt); 2637 putprompt(prompt);
@@ -3047,13 +3057,6 @@ enum {
3047/* c in SIT(c, syntax) must be an *unsigned char* or PEOA or PEOF, 3057/* c in SIT(c, syntax) must be an *unsigned char* or PEOA or PEOF,
3048 * caller must ensure proper cast on it if c is *char_ptr! 3058 * caller must ensure proper cast on it if c is *char_ptr!
3049 */ 3059 */
3050/* Values for syntax param */
3051#define BASESYNTAX 0 /* not in quotes */
3052#define DQSYNTAX 1 /* in double quotes */
3053#define SQSYNTAX 2 /* in single quotes */
3054#define ARISYNTAX 3 /* in arithmetic */
3055#define PSSYNTAX 4 /* prompt. never passed to SIT() */
3056
3057#if USE_SIT_FUNCTION 3060#if USE_SIT_FUNCTION
3058 3061
3059static int 3062static int
@@ -4953,6 +4956,10 @@ cmdputs(const char *s)
4953 /* These can only happen inside quotes */ 4956 /* These can only happen inside quotes */
4954 cc[0] = c; 4957 cc[0] = c;
4955 str = cc; 4958 str = cc;
4959//FIXME:
4960// $ true $$ &
4961// $ <cr>
4962// [1]+ Done true ${\$} <<=== BUG: ${\$} is not a valid way to write $$ (${$} would be ok)
4956 c = '\\'; 4963 c = '\\';
4957 break; 4964 break;
4958 default: 4965 default:
@@ -5134,7 +5141,10 @@ cmdtxt(union node *n)
5134 cmdputs(utoa(n->nfile.fd)); 5141 cmdputs(utoa(n->nfile.fd));
5135 cmdputs(p); 5142 cmdputs(p);
5136 if (n->type == NTOFD || n->type == NFROMFD) { 5143 if (n->type == NTOFD || n->type == NFROMFD) {
5137 cmdputs(utoa(n->ndup.dupfd)); 5144 if (n->ndup.dupfd >= 0)
5145 cmdputs(utoa(n->ndup.dupfd));
5146 else
5147 cmdputs("-");
5138 break; 5148 break;
5139 } 5149 }
5140 n = n->nfile.fname; 5150 n = n->nfile.fname;
@@ -5517,7 +5527,7 @@ stoppedjobs(void)
5517#undef EMPTY 5527#undef EMPTY
5518#undef CLOSED 5528#undef CLOSED
5519#define EMPTY -2 /* marks an unused slot in redirtab */ 5529#define EMPTY -2 /* marks an unused slot in redirtab */
5520#define CLOSED -3 /* marks a slot of previously-closed fd */ 5530#define CLOSED -1 /* marks a slot of previously-closed fd */
5521 5531
5522/* 5532/*
5523 * Handle here documents. Normally we fork off a process to write the 5533 * Handle here documents. Normally we fork off a process to write the
@@ -5713,73 +5723,182 @@ dup2_or_raise(int from, int to)
5713 } 5723 }
5714 return newfd; 5724 return newfd;
5715} 5725}
5726static int
5727fcntl_F_DUPFD(int fd, int avoid_fd)
5728{
5729 int newfd;
5730 repeat:
5731 newfd = fcntl(fd, F_DUPFD, avoid_fd + 1);
5732 if (newfd < 0) {
5733 if (errno == EBUSY)
5734 goto repeat;
5735 if (errno == EINTR)
5736 goto repeat;
5737 }
5738 return newfd;
5739}
5740static int
5741xdup_CLOEXEC_and_close(int fd, int avoid_fd)
5742{
5743 int newfd;
5744 repeat:
5745 newfd = fcntl(fd, F_DUPFD, avoid_fd + 1);
5746 if (newfd < 0) {
5747 if (errno == EBUSY)
5748 goto repeat;
5749 if (errno == EINTR)
5750 goto repeat;
5751 /* fd was not open? */
5752 if (errno == EBADF)
5753 return fd;
5754 ash_msg_and_raise_perror("%d", newfd);
5755 }
5756 fcntl(newfd, F_SETFD, FD_CLOEXEC);
5757 close(fd);
5758 return newfd;
5759}
5716 5760
5717/* Struct def and variable are moved down to the first usage site */ 5761/* Struct def and variable are moved down to the first usage site */
5718struct two_fd_t { 5762struct squirrel {
5719 int orig, copy; 5763 int orig_fd;
5764 int moved_to;
5720}; 5765};
5721struct redirtab { 5766struct redirtab {
5722 struct redirtab *next; 5767 struct redirtab *next;
5723 int pair_count; 5768 int pair_count;
5724 struct two_fd_t two_fd[]; 5769 struct squirrel two_fd[];
5725}; 5770};
5726#define redirlist (G_var.redirlist) 5771#define redirlist (G_var.redirlist)
5727enum {
5728 COPYFD_RESTORE = (int)~(INT_MAX),
5729};
5730 5772
5731static int 5773static void
5732need_to_remember(struct redirtab *rp, int fd) 5774add_squirrel_closed(struct redirtab *sq, int fd)
5733{ 5775{
5734 int i; 5776 int i;
5735 5777
5736 if (!rp) /* remembering was not requested */ 5778 if (!sq)
5737 return 0; 5779 return;
5738 5780
5739 for (i = 0; i < rp->pair_count; i++) { 5781 for (i = 0; sq->two_fd[i].orig_fd != EMPTY; i++) {
5740 if (rp->two_fd[i].orig == fd) { 5782 /* If we collide with an already moved fd... */
5741 /* already remembered */ 5783 if (fd == sq->two_fd[i].orig_fd) {
5742 return 0; 5784 /* Examples:
5785 * "echo 3>FILE 3>&- 3>FILE"
5786 * "echo 3>&- 3>FILE"
5787 * No need for last redirect to insert
5788 * another "need to close 3" indicator.
5789 */
5790 TRACE(("redirect_fd %d: already moved or closed\n", fd));
5791 return;
5743 } 5792 }
5744 } 5793 }
5745 return 1; 5794 TRACE(("redirect_fd %d: previous fd was closed\n", fd));
5795 sq->two_fd[i].orig_fd = fd;
5796 sq->two_fd[i].moved_to = CLOSED;
5746} 5797}
5747 5798
5748/* "hidden" fd is a fd used to read scripts, or a copy of such */
5749static int 5799static int
5750is_hidden_fd(struct redirtab *rp, int fd) 5800save_fd_on_redirect(int fd, int avoid_fd, struct redirtab *sq)
5751{ 5801{
5752 int i; 5802 int i, new_fd;
5753 struct parsefile *pf;
5754 5803
5755 if (fd == -1) 5804 if (avoid_fd < 9) /* the important case here is that it can be -1 */
5805 avoid_fd = 9;
5806
5807#if JOBS
5808 if (fd == ttyfd) {
5809 /* Testcase: "ls -l /proc/$$/fd 10>&-" should work */
5810 ttyfd = xdup_CLOEXEC_and_close(ttyfd, avoid_fd);
5811 TRACE(("redirect_fd %d: matches ttyfd, moving it to %d\n", fd, ttyfd));
5812 return 1; /* "we closed fd" */
5813 }
5814#endif
5815 /* Are we called from redirect(0)? E.g. redirect
5816 * in a forked child. No need to save fds,
5817 * we aren't going to use them anymore, ok to trash.
5818 */
5819 if (!sq)
5756 return 0; 5820 return 0;
5757 /* Check open scripts' fds */ 5821
5758 pf = g_parsefile; 5822 /* If this one of script's fds? */
5759 while (pf) { 5823 if (fd != 0) {
5760 /* We skip pf_fd == 0 case because of the following case: 5824 struct parsefile *pf = g_parsefile;
5761 * $ ash # running ash interactively 5825 while (pf) {
5762 * $ . ./script.sh 5826 /* We skip fd == 0 case because of the following:
5763 * and in script.sh: "exec 9>&0". 5827 * $ ash # running ash interactively
5764 * Even though top-level pf_fd _is_ 0, 5828 * $ . ./script.sh
5765 * it's still ok to use it: "read" builtin uses it, 5829 * and in script.sh: "exec 9>&0".
5766 * why should we cripple "exec" builtin? 5830 * Even though top-level pf_fd _is_ 0,
5767 */ 5831 * it's still ok to use it: "read" builtin uses it,
5768 if (pf->pf_fd > 0 && fd == pf->pf_fd) { 5832 * why should we cripple "exec" builtin?
5769 return 1; 5833 */
5834 if (fd == pf->pf_fd) {
5835 pf->pf_fd = xdup_CLOEXEC_and_close(fd, avoid_fd);
5836 return 1; /* "we closed fd" */
5837 }
5838 pf = pf->prev;
5770 } 5839 }
5771 pf = pf->prev;
5772 } 5840 }
5773 5841
5774 if (!rp) 5842 /* Check whether it collides with any open fds (e.g. stdio), save fds as needed */
5775 return 0; 5843
5776 /* Check saved fds of redirects */ 5844 /* First: do we collide with some already moved fds? */
5777 fd |= COPYFD_RESTORE; 5845 for (i = 0; sq->two_fd[i].orig_fd != EMPTY; i++) {
5778 for (i = 0; i < rp->pair_count; i++) { 5846 /* If we collide with an already moved fd... */
5779 if (rp->two_fd[i].copy == fd) { 5847 if (fd == sq->two_fd[i].moved_to) {
5780 return 1; 5848 new_fd = fcntl_F_DUPFD(fd, avoid_fd);
5849 sq->two_fd[i].moved_to = new_fd;
5850 TRACE(("redirect_fd %d: already busy, moving to %d\n", fd, new_fd));
5851 if (new_fd < 0) /* what? */
5852 xfunc_die();
5853 return 0; /* "we did not close fd" */
5854 }
5855 if (fd == sq->two_fd[i].orig_fd) {
5856 /* Example: echo Hello >/dev/null 1>&2 */
5857 TRACE(("redirect_fd %d: already moved\n", fd));
5858 return 0; /* "we did not close fd" */
5781 } 5859 }
5782 } 5860 }
5861
5862 /* If this fd is open, we move and remember it; if it's closed, new_fd = CLOSED (-1) */
5863 new_fd = fcntl_F_DUPFD(fd, avoid_fd);
5864 TRACE(("redirect_fd %d: previous fd is moved to %d (-1 if it was closed)\n", fd, new_fd));
5865 if (new_fd < 0) {
5866 if (errno != EBADF)
5867 xfunc_die();
5868 /* new_fd = CLOSED; - already is -1 */
5869 }
5870 sq->two_fd[i].moved_to = new_fd;
5871 sq->two_fd[i].orig_fd = fd;
5872
5873 /* if we move stderr, let "set -x" code know */
5874 if (fd == preverrout_fd)
5875 preverrout_fd = new_fd;
5876
5877 return 0; /* "we did not close fd" */
5878}
5879
5880static int
5881internally_opened_fd(int fd, struct redirtab *sq)
5882{
5883 int i;
5884#if JOBS
5885 if (fd == ttyfd)
5886 return 1;
5887#endif
5888 /* If this one of script's fds? */
5889 if (fd != 0) {
5890 struct parsefile *pf = g_parsefile;
5891 while (pf) {
5892 if (fd == pf->pf_fd)
5893 return 1;
5894 pf = pf->prev;
5895 }
5896 }
5897
5898 if (sq) for (i = 0; i < sq->pair_count && sq->two_fd[i].orig_fd != EMPTY; i++) {
5899 if (fd == sq->two_fd[i].moved_to)
5900 return 1;
5901 }
5783 return 0; 5902 return 0;
5784} 5903}
5785 5904
@@ -5790,148 +5909,149 @@ is_hidden_fd(struct redirtab *rp, int fd)
5790 */ 5909 */
5791/* flags passed to redirect */ 5910/* flags passed to redirect */
5792#define REDIR_PUSH 01 /* save previous values of file descriptors */ 5911#define REDIR_PUSH 01 /* save previous values of file descriptors */
5793#define REDIR_SAVEFD2 03 /* set preverrout */
5794static void 5912static void
5795redirect(union node *redir, int flags) 5913redirect(union node *redir, int flags)
5796{ 5914{
5797 struct redirtab *sv; 5915 struct redirtab *sv;
5798 int sv_pos; 5916 int sv_pos;
5799 int i;
5800 int fd;
5801 int newfd;
5802 int copied_fd2 = -1;
5803 5917
5804 if (!redir) { 5918 if (!redir)
5805 return; 5919 return;
5806 }
5807 5920
5808 sv = NULL;
5809 sv_pos = 0; 5921 sv_pos = 0;
5922 sv = NULL;
5810 INT_OFF; 5923 INT_OFF;
5811 if (flags & REDIR_PUSH) { 5924 if (flags & REDIR_PUSH)
5812 union node *tmp = redir; 5925 sv = redirlist;
5813 do {
5814 sv_pos++;
5815#if BASH_REDIR_OUTPUT
5816 if (tmp->nfile.type == NTO2)
5817 sv_pos++;
5818#endif
5819 tmp = tmp->nfile.next;
5820 } while (tmp);
5821 sv = ckmalloc(sizeof(*sv) + sv_pos * sizeof(sv->two_fd[0]));
5822 sv->next = redirlist;
5823 sv->pair_count = sv_pos;
5824 redirlist = sv;
5825 while (sv_pos > 0) {
5826 sv_pos--;
5827 sv->two_fd[sv_pos].orig = sv->two_fd[sv_pos].copy = EMPTY;
5828 }
5829 }
5830
5831 do { 5926 do {
5832 int right_fd = -1; 5927 int fd;
5928 int newfd;
5929 int close_fd;
5930 int closed;
5931
5833 fd = redir->nfile.fd; 5932 fd = redir->nfile.fd;
5834 if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) { 5933 if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) {
5835 right_fd = redir->ndup.dupfd; 5934 //bb_error_msg("doing %d > %d", fd, newfd);
5836 //bb_error_msg("doing %d > %d", fd, right_fd); 5935 newfd = redir->ndup.dupfd;
5837 /* redirect from/to same file descriptor? */ 5936 close_fd = -1;
5838 if (right_fd == fd)
5839 continue;
5840 /* "echo >&10" and 10 is a fd opened to a sh script? */
5841 if (is_hidden_fd(sv, right_fd)) {
5842 errno = EBADF; /* as if it is closed */
5843 ash_msg_and_raise_perror("%d", right_fd);
5844 }
5845 newfd = -1;
5846 } else { 5937 } else {
5847 newfd = openredirect(redir); /* always >= 0 */ 5938 newfd = openredirect(redir); /* always >= 0 */
5848 if (fd == newfd) { 5939 if (fd == newfd) {
5849 /* Descriptor wasn't open before redirect. 5940 /* open() gave us precisely the fd we wanted.
5850 * Mark it for close in the future */ 5941 * This means that this fd was not busy
5851 if (need_to_remember(sv, fd)) { 5942 * (not opened to anywhere).
5852 goto remember_to_close; 5943 * Remember to close it on restore:
5853 } 5944 */
5945 add_squirrel_closed(sv, fd);
5854 continue; 5946 continue;
5855 } 5947 }
5948 close_fd = newfd;
5856 } 5949 }
5857#if BASH_REDIR_OUTPUT 5950
5858 redirect_more: 5951 if (fd == newfd)
5859#endif 5952 continue;
5860 if (need_to_remember(sv, fd)) { 5953
5861 /* Copy old descriptor */ 5954 /* if "N>FILE": move newfd to fd */
5862 /* Careful to not accidentally "save" 5955 /* if "N>&M": dup newfd to fd */
5863 * to the same fd as right side fd in N>&M */ 5956 /* if "N>&-": close fd (newfd is -1) */
5864 int minfd = right_fd < 10 ? 10 : right_fd + 1; 5957
5865#if defined(F_DUPFD_CLOEXEC) 5958 IF_BASH_REDIR_OUTPUT(redirect_more:)
5866 i = fcntl(fd, F_DUPFD_CLOEXEC, minfd); 5959
5867#else 5960 closed = save_fd_on_redirect(fd, /*avoid:*/ newfd, sv);
5868 i = fcntl(fd, F_DUPFD, minfd); 5961 if (newfd == -1) {
5869#endif 5962 /* "N>&-" means "close me" */
5870 if (i == -1) { 5963 if (!closed) {
5871 i = errno; 5964 /* ^^^ optimization: saving may already
5872 if (i != EBADF) { 5965 * have closed it. If not... */
5873 /* Strange error (e.g. "too many files" EMFILE?) */ 5966 close(fd);
5874 if (newfd >= 0) 5967 }
5875 close(newfd); 5968 } else {
5876 errno = i; 5969 /* if newfd is a script fd or saved fd, simulate EBADF */
5877 ash_msg_and_raise_perror("%d", fd); 5970 if (internally_opened_fd(newfd, sv)) {
5878 /* NOTREACHED */ 5971 errno = EBADF;
5879 } 5972 ash_msg_and_raise_perror("%d", newfd);
5880 /* EBADF: it is not open - good, remember to close it */
5881 remember_to_close:
5882 i = CLOSED;
5883 } else { /* fd is open, save its copy */
5884#if !defined(F_DUPFD_CLOEXEC)
5885 fcntl(i, F_SETFD, FD_CLOEXEC);
5886#endif
5887 /* "exec fd>&-" should not close fds
5888 * which point to script file(s).
5889 * Force them to be restored afterwards */
5890 if (is_hidden_fd(sv, fd))
5891 i |= COPYFD_RESTORE;
5892 }
5893 if (fd == 2)
5894 copied_fd2 = i;
5895 sv->two_fd[sv_pos].orig = fd;
5896 sv->two_fd[sv_pos].copy = i;
5897 sv_pos++;
5898 }
5899 if (newfd < 0) {
5900 /* NTOFD/NFROMFD: copy redir->ndup.dupfd to fd */
5901 if (redir->ndup.dupfd < 0) { /* "fd>&-" */
5902 /* Don't want to trigger debugging */
5903 if (fd != -1)
5904 close(fd);
5905 } else {
5906 dup2_or_raise(redir->ndup.dupfd, fd);
5907 } 5973 }
5908 } else if (fd != newfd) { /* move newfd to fd */
5909 dup2_or_raise(newfd, fd); 5974 dup2_or_raise(newfd, fd);
5975 if (close_fd >= 0) /* "N>FILE" or ">&FILE" or heredoc? */
5976 close(close_fd);
5910#if BASH_REDIR_OUTPUT 5977#if BASH_REDIR_OUTPUT
5911 if (!(redir->nfile.type == NTO2 && fd == 2)) 5978 if (redir->nfile.type == NTO2 && fd == 1) {
5979 /* ">&FILE". we already redirected to 1, now copy 1 to 2 */
5980 fd = 2;
5981 newfd = 1;
5982 close_fd = -1;
5983 goto redirect_more;
5984 }
5912#endif 5985#endif
5913 close(newfd);
5914 } 5986 }
5987 } while ((redir = redir->nfile.next) != NULL);
5988 INT_ON;
5989
5990//dash:#define REDIR_SAVEFD2 03 /* set preverrout */
5991#define REDIR_SAVEFD2 0
5992 // dash has a bug: since REDIR_SAVEFD2=3 and REDIR_PUSH=1, this test
5993 // triggers for pure REDIR_PUSH too. Thus, this is done almost always,
5994 // not only for calls with flags containing REDIR_SAVEFD2.
5995 // We do this unconditionally (see save_fd_on_redirect()).
5996 //if ((flags & REDIR_SAVEFD2) && copied_fd2 >= 0)
5997 // preverrout_fd = copied_fd2;
5998}
5999
6000static int
6001redirectsafe(union node *redir, int flags)
6002{
6003 int err;
6004 volatile int saveint;
6005 struct jmploc *volatile savehandler = exception_handler;
6006 struct jmploc jmploc;
6007
6008 SAVE_INT(saveint);
6009 /* "echo 9>/dev/null; echo >&9; echo result: $?" - result should be 1, not 2! */
6010 err = setjmp(jmploc.loc); /* was = setjmp(jmploc.loc) * 2; */
6011 if (!err) {
6012 exception_handler = &jmploc;
6013 redirect(redir, flags);
6014 }
6015 exception_handler = savehandler;
6016 if (err && exception_type != EXERROR)
6017 longjmp(exception_handler->loc, 1);
6018 RESTORE_INT(saveint);
6019 return err;
6020}
6021
6022static struct redirtab*
6023pushredir(union node *redir)
6024{
6025 struct redirtab *sv;
6026 int i;
6027
6028 if (!redir)
6029 return redirlist;
6030
6031 i = 0;
6032 do {
6033 i++;
5915#if BASH_REDIR_OUTPUT 6034#if BASH_REDIR_OUTPUT
5916 if (redir->nfile.type == NTO2 && fd == 1) { 6035 if (redir->nfile.type == NTO2)
5917 /* We already redirected it to fd 1, now copy it to 2 */ 6036 i++;
5918 newfd = 1;
5919 fd = 2;
5920 goto redirect_more;
5921 }
5922#endif 6037#endif
5923 } while ((redir = redir->nfile.next) != NULL); 6038 redir = redir->nfile.next;
6039 } while (redir);
5924 6040
5925 INT_ON; 6041 sv = ckzalloc(sizeof(*sv) + i * sizeof(sv->two_fd[0]));
5926 if ((flags & REDIR_SAVEFD2) && copied_fd2 >= 0) 6042 sv->pair_count = i;
5927 preverrout_fd = copied_fd2; 6043 while (--i >= 0)
6044 sv->two_fd[i].orig_fd = sv->two_fd[i].moved_to = EMPTY;
6045 sv->next = redirlist;
6046 redirlist = sv;
6047 return sv->next;
5928} 6048}
5929 6049
5930/* 6050/*
5931 * Undo the effects of the last redirection. 6051 * Undo the effects of the last redirection.
5932 */ 6052 */
5933static void 6053static void
5934popredir(int drop, int restore) 6054popredir(int drop)
5935{ 6055{
5936 struct redirtab *rp; 6056 struct redirtab *rp;
5937 int i; 6057 int i;
@@ -5941,20 +6061,19 @@ popredir(int drop, int restore)
5941 INT_OFF; 6061 INT_OFF;
5942 rp = redirlist; 6062 rp = redirlist;
5943 for (i = 0; i < rp->pair_count; i++) { 6063 for (i = 0; i < rp->pair_count; i++) {
5944 int fd = rp->two_fd[i].orig; 6064 int fd = rp->two_fd[i].orig_fd;
5945 int copy = rp->two_fd[i].copy; 6065 int copy = rp->two_fd[i].moved_to;
5946 if (copy == CLOSED) { 6066 if (copy == CLOSED) {
5947 if (!drop) 6067 if (!drop)
5948 close(fd); 6068 close(fd);
5949 continue; 6069 continue;
5950 } 6070 }
5951 if (copy != EMPTY) { 6071 if (copy != EMPTY) {
5952 if (!drop || (restore && (copy & COPYFD_RESTORE))) { 6072 if (!drop) {
5953 copy &= ~COPYFD_RESTORE;
5954 /*close(fd);*/ 6073 /*close(fd);*/
5955 dup2_or_raise(copy, fd); 6074 dup2_or_raise(copy, fd);
5956 } 6075 }
5957 close(copy & ~COPYFD_RESTORE); 6076 close(copy);
5958 } 6077 }
5959 } 6078 }
5960 redirlist = rp->next; 6079 redirlist = rp->next;
@@ -5962,30 +6081,11 @@ popredir(int drop, int restore)
5962 INT_ON; 6081 INT_ON;
5963} 6082}
5964 6083
5965/* 6084static void
5966 * Undo all redirections. Called on error or interrupt. 6085unwindredir(struct redirtab *stop)
5967 */
5968
5969static int
5970redirectsafe(union node *redir, int flags)
5971{ 6086{
5972 int err; 6087 while (redirlist != stop)
5973 volatile int saveint; 6088 popredir(/*drop:*/ 0);
5974 struct jmploc *volatile savehandler = exception_handler;
5975 struct jmploc jmploc;
5976
5977 SAVE_INT(saveint);
5978 /* "echo 9>/dev/null; echo >&9; echo result: $?" - result should be 1, not 2! */
5979 err = setjmp(jmploc.loc); // huh?? was = setjmp(jmploc.loc) * 2;
5980 if (!err) {
5981 exception_handler = &jmploc;
5982 redirect(redir, flags);
5983 }
5984 exception_handler = savehandler;
5985 if (err && exception_type != EXERROR)
5986 longjmp(exception_handler->loc, 1);
5987 RESTORE_INT(saveint);
5988 return err;
5989} 6089}
5990 6090
5991 6091
@@ -8076,7 +8176,7 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) const char *cmd, char **argv, c
8076 clearenv(); 8176 clearenv();
8077 while (*envp) 8177 while (*envp)
8078 putenv(*envp++); 8178 putenv(*envp++);
8079 popredir(/*drop:*/ 1, /*restore:*/ 0); 8179 popredir(/*drop:*/ 1);
8080 run_applet_no_and_exit(applet_no, cmd, argv); 8180 run_applet_no_and_exit(applet_no, cmd, argv);
8081 } 8181 }
8082 /* re-exec ourselves with the new arguments */ 8182 /* re-exec ourselves with the new arguments */
@@ -9159,12 +9259,13 @@ evaltree(union node *n, int flags)
9159 goto setstatus; 9259 goto setstatus;
9160 case NREDIR: 9260 case NREDIR:
9161 expredir(n->nredir.redirect); 9261 expredir(n->nredir.redirect);
9262 pushredir(n->nredir.redirect);
9162 status = redirectsafe(n->nredir.redirect, REDIR_PUSH); 9263 status = redirectsafe(n->nredir.redirect, REDIR_PUSH);
9163 if (!status) { 9264 if (!status) {
9164 status = evaltree(n->nredir.n, flags & EV_TESTED); 9265 status = evaltree(n->nredir.n, flags & EV_TESTED);
9165 } 9266 }
9166 if (n->nredir.redirect) 9267 if (n->nredir.redirect)
9167 popredir(/*drop:*/ 0, /*restore:*/ 0 /* not sure */); 9268 popredir(/*drop:*/ 0);
9168 goto setstatus; 9269 goto setstatus;
9169 case NCMD: 9270 case NCMD:
9170 evalfn = evalcommand; 9271 evalfn = evalcommand;
@@ -10079,6 +10180,7 @@ evalcommand(union node *cmd, int flags)
10079 "\0\0", bltincmd /* why three NULs? */ 10180 "\0\0", bltincmd /* why three NULs? */
10080 }; 10181 };
10081 struct localvar_list *localvar_stop; 10182 struct localvar_list *localvar_stop;
10183 struct redirtab *redir_stop;
10082 struct stackmark smark; 10184 struct stackmark smark;
10083 union node *argp; 10185 union node *argp;
10084 struct arglist arglist; 10186 struct arglist arglist;
@@ -10093,9 +10195,7 @@ evalcommand(union node *cmd, int flags)
10093 int spclbltin; 10195 int spclbltin;
10094 int status; 10196 int status;
10095 char **nargv; 10197 char **nargv;
10096 struct builtincmd *bcmd;
10097 smallint cmd_is_exec; 10198 smallint cmd_is_exec;
10098 smallint pseudovarflag = 0;
10099 10199
10100 /* First expand the arguments. */ 10200 /* First expand the arguments. */
10101 TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags)); 10201 TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags));
@@ -10112,21 +10212,24 @@ evalcommand(union node *cmd, int flags)
10112 10212
10113 argc = 0; 10213 argc = 0;
10114 if (cmd->ncmd.args) { 10214 if (cmd->ncmd.args) {
10215 struct builtincmd *bcmd;
10216 smallint pseudovarflag;
10217
10115 bcmd = find_builtin(cmd->ncmd.args->narg.text); 10218 bcmd = find_builtin(cmd->ncmd.args->narg.text);
10116 pseudovarflag = bcmd && IS_BUILTIN_ASSIGN(bcmd); 10219 pseudovarflag = bcmd && IS_BUILTIN_ASSIGN(bcmd);
10117 }
10118 10220
10119 for (argp = cmd->ncmd.args; argp; argp = argp->narg.next) { 10221 for (argp = cmd->ncmd.args; argp; argp = argp->narg.next) {
10120 struct strlist **spp; 10222 struct strlist **spp;
10121 10223
10122 spp = arglist.lastp; 10224 spp = arglist.lastp;
10123 if (pseudovarflag && isassignment(argp->narg.text)) 10225 if (pseudovarflag && isassignment(argp->narg.text))
10124 expandarg(argp, &arglist, EXP_VARTILDE); 10226 expandarg(argp, &arglist, EXP_VARTILDE);
10125 else 10227 else
10126 expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); 10228 expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
10127 10229
10128 for (sp = *spp; sp; sp = sp->next) 10230 for (sp = *spp; sp; sp = sp->next)
10129 argc++; 10231 argc++;
10232 }
10130 } 10233 }
10131 10234
10132 /* Reserve one extra spot at the front for shellexec. */ 10235 /* Reserve one extra spot at the front for shellexec. */
@@ -10142,8 +10245,9 @@ evalcommand(union node *cmd, int flags)
10142 if (iflag && funcnest == 0 && argc > 0) 10245 if (iflag && funcnest == 0 && argc > 0)
10143 lastarg = nargv[-1]; 10246 lastarg = nargv[-1];
10144 10247
10145 preverrout_fd = 2;
10146 expredir(cmd->ncmd.redirect); 10248 expredir(cmd->ncmd.redirect);
10249 redir_stop = pushredir(cmd->ncmd.redirect);
10250 preverrout_fd = 2;
10147 status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH | REDIR_SAVEFD2); 10251 status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH | REDIR_SAVEFD2);
10148 10252
10149 path = vpath.var_text; 10253 path = vpath.var_text;
@@ -10169,7 +10273,7 @@ evalcommand(union node *cmd, int flags)
10169 if (xflag) { 10273 if (xflag) {
10170 const char *pfx = ""; 10274 const char *pfx = "";
10171 10275
10172 fdprintf(preverrout_fd, "%s", expandstr(ps4val())); 10276 fdprintf(preverrout_fd, "%s", expandstr(ps4val(), DQSYNTAX));
10173 10277
10174 sp = varlist.list; 10278 sp = varlist.list;
10175 while (sp) { 10279 while (sp) {
@@ -10365,7 +10469,8 @@ evalcommand(union node *cmd, int flags)
10365 10469
10366 out: 10470 out:
10367 if (cmd->ncmd.redirect) 10471 if (cmd->ncmd.redirect)
10368 popredir(/*drop:*/ cmd_is_exec, /*restore:*/ cmd_is_exec); 10472 popredir(/*drop:*/ cmd_is_exec);
10473 unwindredir(redir_stop);
10369 unwindlocalvars(localvar_stop); 10474 unwindlocalvars(localvar_stop);
10370 if (lastarg) { 10475 if (lastarg) {
10371 /* dsl: I think this is intended to be used to support 10476 /* dsl: I think this is intended to be used to support
@@ -10482,6 +10587,7 @@ static smallint checkkwd;
10482#define CHKALIAS 0x1 10587#define CHKALIAS 0x1
10483#define CHKKWD 0x2 10588#define CHKKWD 0x2
10484#define CHKNL 0x4 10589#define CHKNL 0x4
10590#define CHKEOFMARK 0x8
10485 10591
10486/* 10592/*
10487 * Push a string back onto the input at this current parsefile level. 10593 * Push a string back onto the input at this current parsefile level.
@@ -10794,31 +10900,6 @@ pgetc_without_PEOA(void)
10794#endif 10900#endif
10795 10901
10796/* 10902/*
10797 * Read a line from the script.
10798 */
10799static char *
10800pfgets(char *line, int len)
10801{
10802 char *p = line;
10803 int nleft = len;
10804 int c;
10805
10806 while (--nleft > 0) {
10807 c = pgetc_without_PEOA();
10808 if (c == PEOF) {
10809 if (p == line)
10810 return NULL;
10811 break;
10812 }
10813 *p++ = c;
10814 if (c == '\n')
10815 break;
10816 }
10817 *p = '\0';
10818 return line;
10819}
10820
10821/*
10822 * Undo a call to pgetc. Only two characters may be pushed back. 10903 * Undo a call to pgetc. Only two characters may be pushed back.
10823 * PEOF may be pushed back. 10904 * PEOF may be pushed back.
10824 */ 10905 */
@@ -11127,7 +11208,7 @@ setoption(int flag, int val)
11127 /* NOTREACHED */ 11208 /* NOTREACHED */
11128} 11209}
11129static int 11210static int
11130options(int cmdline) 11211options(int cmdline, int *login_sh)
11131{ 11212{
11132 char *p; 11213 char *p;
11133 int val; 11214 int val;
@@ -11168,11 +11249,14 @@ options(int cmdline)
11168 if (*argptr) 11249 if (*argptr)
11169 argptr++; 11250 argptr++;
11170 } else if (cmdline && (c == 'l')) { /* -l or +l == --login */ 11251 } else if (cmdline && (c == 'l')) { /* -l or +l == --login */
11171 isloginsh = 1; 11252 if (login_sh)
11253 *login_sh = 1;
11172 /* bash does not accept +-login, we also won't */ 11254 /* bash does not accept +-login, we also won't */
11173 } else if (cmdline && val && (c == '-')) { /* long options */ 11255 } else if (cmdline && val && (c == '-')) { /* long options */
11174 if (strcmp(p, "login") == 0) 11256 if (strcmp(p, "login") == 0) {
11175 isloginsh = 1; 11257 if (login_sh)
11258 *login_sh = 1;
11259 }
11176 break; 11260 break;
11177 } else { 11261 } else {
11178 setoption(c, val); 11262 setoption(c, val);
@@ -11256,7 +11340,7 @@ setcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
11256 return showvars(nullstr, 0, VUNSET); 11340 return showvars(nullstr, 0, VUNSET);
11257 11341
11258 INT_OFF; 11342 INT_OFF;
11259 retval = options(/*cmdline:*/ 0); 11343 retval = options(/*cmdline:*/ 0, NULL);
11260 if (retval == 0) { /* if no parse error... */ 11344 if (retval == 0) { /* if no parse error... */
11261 optschanged(); 11345 optschanged();
11262 if (*argptr != NULL) { 11346 if (*argptr != NULL) {
@@ -11453,8 +11537,6 @@ raise_error_unexpected_syntax(int token)
11453 /* NOTREACHED */ 11537 /* NOTREACHED */
11454} 11538}
11455 11539
11456#define EOFMARKLEN 79
11457
11458/* parsing is heavily cross-recursive, need these forward decls */ 11540/* parsing is heavily cross-recursive, need these forward decls */
11459static union node *andor(void); 11541static union node *andor(void);
11460static union node *pipeline(void); 11542static union node *pipeline(void);
@@ -11634,43 +11716,22 @@ fixredir(union node *n, const char *text, int err)
11634 } 11716 }
11635} 11717}
11636 11718
11637/*
11638 * Returns true if the text contains nothing to expand (no dollar signs
11639 * or backquotes).
11640 */
11641static int
11642noexpand(const char *text)
11643{
11644 unsigned char c;
11645
11646 while ((c = *text++) != '\0') {
11647 if (c == CTLQUOTEMARK)
11648 continue;
11649 if (c == CTLESC)
11650 text++;
11651 else if (SIT(c, BASESYNTAX) == CCTL)
11652 return 0;
11653 }
11654 return 1;
11655}
11656
11657static void 11719static void
11658parsefname(void) 11720parsefname(void)
11659{ 11721{
11660 union node *n = redirnode; 11722 union node *n = redirnode;
11661 11723
11724 if (n->type == NHERE)
11725 checkkwd = CHKEOFMARK;
11662 if (readtoken() != TWORD) 11726 if (readtoken() != TWORD)
11663 raise_error_unexpected_syntax(-1); 11727 raise_error_unexpected_syntax(-1);
11664 if (n->type == NHERE) { 11728 if (n->type == NHERE) {
11665 struct heredoc *here = heredoc; 11729 struct heredoc *here = heredoc;
11666 struct heredoc *p; 11730 struct heredoc *p;
11667 int i;
11668 11731
11669 if (quoteflag == 0) 11732 if (quoteflag == 0)
11670 n->type = NXHERE; 11733 n->type = NXHERE;
11671 TRACE(("Here document %d\n", n->type)); 11734 TRACE(("Here document %d\n", n->type));
11672 if (!noexpand(wordtext) || (i = strlen(wordtext)) == 0 || i > EOFMARKLEN)
11673 raise_error_syntax("illegal eof marker for << redirection");
11674 rmescapes(wordtext, 0); 11735 rmescapes(wordtext, 0);
11675 here->eofmark = wordtext; 11736 here->eofmark = wordtext;
11676 here->next = NULL; 11737 here->next = NULL;
@@ -12062,6 +12123,15 @@ decode_dollar_squote(void)
12062} 12123}
12063#endif 12124#endif
12064 12125
12126/* Used by expandstr to get here-doc like behaviour. */
12127#define FAKEEOFMARK ((char*)(uintptr_t)1)
12128
12129static ALWAYS_INLINE int
12130realeofmark(const char *eofmark)
12131{
12132 return eofmark && eofmark != FAKEEOFMARK;
12133}
12134
12065/* 12135/*
12066 * If eofmark is NULL, read a word or a redirection symbol. If eofmark 12136 * If eofmark is NULL, read a word or a redirection symbol. If eofmark
12067 * is not NULL, read a here document. In the latter case, eofmark is the 12137 * is not NULL, read a here document. In the latter case, eofmark is the
@@ -12086,7 +12156,6 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
12086 /* c parameter is an unsigned char or PEOF or PEOA */ 12156 /* c parameter is an unsigned char or PEOF or PEOA */
12087 char *out; 12157 char *out;
12088 size_t len; 12158 size_t len;
12089 char line[EOFMARKLEN + 1];
12090 struct nodelist *bqlist; 12159 struct nodelist *bqlist;
12091 smallint quotef; 12160 smallint quotef;
12092 smallint dblquote; 12161 smallint dblquote;
@@ -12104,9 +12173,13 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
12104 bqlist = NULL; 12173 bqlist = NULL;
12105 quotef = 0; 12174 quotef = 0;
12106 IF_FEATURE_SH_MATH(prevsyntax = 0;) 12175 IF_FEATURE_SH_MATH(prevsyntax = 0;)
12176#if ENABLE_ASH_EXPAND_PRMT
12107 pssyntax = (syntax == PSSYNTAX); 12177 pssyntax = (syntax == PSSYNTAX);
12108 if (pssyntax) 12178 if (pssyntax)
12109 syntax = DQSYNTAX; 12179 syntax = DQSYNTAX;
12180#else
12181 pssyntax = 0; /* constant */
12182#endif
12110 dblquote = (syntax == DQSYNTAX); 12183 dblquote = (syntax == DQSYNTAX);
12111 varnest = 0; 12184 varnest = 0;
12112 IF_FEATURE_SH_MATH(arinest = 0;) 12185 IF_FEATURE_SH_MATH(arinest = 0;)
@@ -12160,7 +12233,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
12160 } else if (c == '\n') { 12233 } else if (c == '\n') {
12161 nlprompt(); 12234 nlprompt();
12162 } else { 12235 } else {
12163 if (c == '$' && pssyntax) { 12236 if (pssyntax && c == '$') {
12164 USTPUTC(CTLESC, out); 12237 USTPUTC(CTLESC, out);
12165 USTPUTC('\\', out); 12238 USTPUTC('\\', out);
12166 } 12239 }
@@ -12308,7 +12381,10 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
12308 * we are at the end of the here document, this routine sets the c to PEOF. 12381 * we are at the end of the here document, this routine sets the c to PEOF.
12309 */ 12382 */
12310checkend: { 12383checkend: {
12311 if (eofmark) { 12384 if (realeofmark(eofmark)) {
12385 int markloc;
12386 char *p;
12387
12312#if ENABLE_ASH_ALIAS 12388#if ENABLE_ASH_ALIAS
12313 if (c == PEOA) 12389 if (c == PEOA)
12314 c = pgetc_without_PEOA(); 12390 c = pgetc_without_PEOA();
@@ -12318,27 +12394,42 @@ checkend: {
12318 c = pgetc_without_PEOA(); 12394 c = pgetc_without_PEOA();
12319 } 12395 }
12320 } 12396 }
12321 if (c == *eofmark) {
12322 if (pfgets(line, sizeof(line)) != NULL) {
12323 char *p, *q;
12324 int cc;
12325 12397
12326 p = line; 12398 markloc = out - (char *)stackblock();
12327 for (q = eofmark + 1;; p++, q++) { 12399 for (p = eofmark; STPUTC(c, out), *p; p++) {
12328 cc = *p; 12400 if (c != *p)
12329 if (cc == '\n') 12401 goto more_heredoc;
12330 cc = 0; 12402
12331 if (!*q || cc != *q) 12403 c = pgetc_without_PEOA();
12332 break; 12404 }
12333 } 12405
12334 if (cc == *q) { 12406 if (c == '\n' || c == PEOF) {
12335 c = PEOF; 12407 c = PEOF;
12336 nlnoprompt(); 12408 g_parsefile->linno++;
12337 } else { 12409 needprompt = doprompt;
12338 pushstring(line, NULL); 12410 } else {
12411 int len_here;
12412
12413 more_heredoc:
12414 p = (char *)stackblock() + markloc + 1;
12415 len_here = out - p;
12416
12417 if (len_here) {
12418 len_here -= (c >= PEOF);
12419 c = p[-1];
12420
12421 if (len_here) {
12422 char *str;
12423
12424 str = alloca(len_here + 1);
12425 *(char *)mempcpy(str, p, len_here) = '\0';
12426
12427 pushstring(str, NULL);
12339 } 12428 }
12340 } 12429 }
12341 } 12430 }
12431
12432 STADJUST((char *)stackblock() + markloc - out, out);
12342 } 12433 }
12343 goto checkend_return; 12434 goto checkend_return;
12344} 12435}
@@ -12432,7 +12523,8 @@ parsesub: {
12432 int typeloc; 12523 int typeloc;
12433 12524
12434 c = pgetc_eatbnl(); 12525 c = pgetc_eatbnl();
12435 if (c > 255 /* PEOA or PEOF */ 12526 if ((checkkwd & CHKEOFMARK)
12527 || c > 255 /* PEOA or PEOF */
12436 || (c != '(' && c != '{' && !is_name(c) && !is_special(c)) 12528 || (c != '(' && c != '{' && !is_name(c) && !is_special(c))
12437 ) { 12529 ) {
12438#if BASH_DOLLAR_SQUOTE 12530#if BASH_DOLLAR_SQUOTE
@@ -12994,22 +13086,18 @@ parseheredoc(void)
12994} 13086}
12995 13087
12996 13088
12997/*
12998 * called by editline -- any expansions to the prompt should be added here.
12999 */
13000static const char * 13089static const char *
13001expandstr(const char *ps) 13090expandstr(const char *ps, int syntax_type)
13002{ 13091{
13003 union node n; 13092 union node n;
13004 int saveprompt; 13093 int saveprompt;
13005 13094
13006 /* XXX Fix (char *) cast. It _is_ a bug. ps is variable's value, 13095 /* XXX Fix (char *) cast. */
13007 * and token processing _can_ alter it (delete NULs etc). */
13008 setinputstring((char *)ps); 13096 setinputstring((char *)ps);
13009 13097
13010 saveprompt = doprompt; 13098 saveprompt = doprompt;
13011 doprompt = 0; 13099 doprompt = 0;
13012 readtoken1(pgetc(), PSSYNTAX, nullstr, 0); 13100 readtoken1(pgetc(), syntax_type, FAKEEOFMARK, 0);
13013 doprompt = saveprompt; 13101 doprompt = saveprompt;
13014 13102
13015 popfile(); 13103 popfile();
@@ -14133,21 +14221,23 @@ init(void)
14133/* 14221/*
14134 * Process the shell command line arguments. 14222 * Process the shell command line arguments.
14135 */ 14223 */
14136static void 14224static int
14137procargs(char **argv) 14225procargs(char **argv)
14138{ 14226{
14139 int i; 14227 int i;
14140 const char *xminusc; 14228 const char *xminusc;
14141 char **xargv; 14229 char **xargv;
14230 int login_sh;
14142 14231
14143 xargv = argv; 14232 xargv = argv;
14233 login_sh = xargv[0] && xargv[0][0] == '-';
14144 arg0 = xargv[0]; 14234 arg0 = xargv[0];
14145 /* if (xargv[0]) - mmm, this is always true! */ 14235 /* if (xargv[0]) - mmm, this is always true! */
14146 xargv++; 14236 xargv++;
14147 for (i = 0; i < NOPTS; i++) 14237 for (i = 0; i < NOPTS; i++)
14148 optlist[i] = 2; 14238 optlist[i] = 2;
14149 argptr = xargv; 14239 argptr = xargv;
14150 if (options(/*cmdline:*/ 1)) { 14240 if (options(/*cmdline:*/ 1, &login_sh)) {
14151 /* it already printed err message */ 14241 /* it already printed err message */
14152 raise_exception(EXERROR); 14242 raise_exception(EXERROR);
14153 } 14243 }
@@ -14191,6 +14281,8 @@ procargs(char **argv)
14191 xargv++; 14281 xargv++;
14192 } 14282 }
14193 optschanged(); 14283 optschanged();
14284
14285 return login_sh;
14194} 14286}
14195 14287
14196/* 14288/*
@@ -14199,7 +14291,7 @@ procargs(char **argv)
14199static void 14291static void
14200read_profile(const char *name) 14292read_profile(const char *name)
14201{ 14293{
14202 name = expandstr(name); 14294 name = expandstr(name, DQSYNTAX);
14203 if (setinputfile(name, INPUT_PUSH_FILE | INPUT_NOFILE_OK) < 0) 14295 if (setinputfile(name, INPUT_PUSH_FILE | INPUT_NOFILE_OK) < 0)
14204 return; 14296 return;
14205 cmdloop(0); 14297 cmdloop(0);
@@ -14227,8 +14319,7 @@ reset(void)
14227 popallfiles(); 14319 popallfiles();
14228 14320
14229 /* from redir.c: */ 14321 /* from redir.c: */
14230 while (redirlist) 14322 unwindredir(NULL);
14231 popredir(/*drop:*/ 0, /*restore:*/ 0);
14232 14323
14233 /* from var.c: */ 14324 /* from var.c: */
14234 unwindlocalvars(NULL); 14325 unwindlocalvars(NULL);
@@ -14252,6 +14343,7 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
14252 volatile smallint state; 14343 volatile smallint state;
14253 struct jmploc jmploc; 14344 struct jmploc jmploc;
14254 struct stackmark smark; 14345 struct stackmark smark;
14346 int login_sh;
14255 14347
14256 /* Initialize global data */ 14348 /* Initialize global data */
14257 INIT_G_misc(); 14349 INIT_G_misc();
@@ -14304,6 +14396,7 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
14304#if ENABLE_PLATFORM_MINGW32 14396#if ENABLE_PLATFORM_MINGW32
14305 hSIGINT = CreateEvent(NULL, TRUE, FALSE, NULL); 14397 hSIGINT = CreateEvent(NULL, TRUE, FALSE, NULL);
14306 SetConsoleCtrlHandler(ctrl_handler, TRUE); 14398 SetConsoleCtrlHandler(ctrl_handler, TRUE);
14399
14307 if (argc == 3 && !strcmp(argv[1], "--forkshell")) { 14400 if (argc == 3 && !strcmp(argv[1], "--forkshell")) {
14308 forkshell_init(argv[2]); 14401 forkshell_init(argv[2]);
14309 14402
@@ -14311,7 +14404,7 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
14311 bb_error_msg_and_die("subshell ended unexpectedly"); 14404 bb_error_msg_and_die("subshell ended unexpectedly");
14312 } 14405 }
14313#endif 14406#endif
14314 procargs(argv); 14407 login_sh = procargs(argv);
14315#if DEBUG 14408#if DEBUG
14316 TRACE(("Shell args: ")); 14409 TRACE(("Shell args: "));
14317 trace_puts_args(argv); 14410 trace_puts_args(argv);
@@ -14327,9 +14420,7 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
14327 } 14420 }
14328#endif 14421#endif
14329 14422
14330 if (argv[0] && argv[0][0] == '-') 14423 if (login_sh) {
14331 isloginsh = 1;
14332 if (isloginsh) {
14333 const char *hp; 14424 const char *hp;
14334 14425
14335#if ENABLE_PLATFORM_MINGW32 14426#if ENABLE_PLATFORM_MINGW32
@@ -14367,7 +14458,7 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
14367 * Testcase: ash -c 'exec 1>&0' must not complain. */ 14458 * Testcase: ash -c 'exec 1>&0' must not complain. */
14368 // if (!sflag) g_parsefile->pf_fd = -1; 14459 // if (!sflag) g_parsefile->pf_fd = -1;
14369 // ^^ not necessary since now we special-case fd 0 14460 // ^^ not necessary since now we special-case fd 0
14370 // in is_hidden_fd() to not be considered "hidden fd" 14461 // in save_fd_on_redirect()
14371 evalstring(minusc, sflag ? 0 : EV_EXIT); 14462 evalstring(minusc, sflag ? 0 : EV_EXIT);
14372 } 14463 }
14373 14464
diff --git a/shell/ash_test/ash-heredoc/heredoc_empty.right b/shell/ash_test/ash-heredoc/heredoc_empty.right
new file mode 100644
index 000000000..0eabe3671
--- /dev/null
+++ b/shell/ash_test/ash-heredoc/heredoc_empty.right
@@ -0,0 +1,3 @@
1OK
2OK
3OK
diff --git a/shell/ash_test/ash-heredoc/heredoc_empty.tests b/shell/ash_test/ash-heredoc/heredoc_empty.tests
new file mode 100755
index 000000000..3629bc6d1
--- /dev/null
+++ b/shell/ash_test/ash-heredoc/heredoc_empty.tests
@@ -0,0 +1,8 @@
1unset a
2cat <<- $a
3 OK
4$a
5cat <<- ""
6 OK
7
8echo OK
diff --git a/shell/ash_test/ash-heredoc/heredoc_empty2.right b/shell/ash_test/ash-heredoc/heredoc_empty2.right
new file mode 100644
index 000000000..e32c6ea58
--- /dev/null
+++ b/shell/ash_test/ash-heredoc/heredoc_empty2.right
@@ -0,0 +1,4 @@
1OK1
2Ok:0
3OK2
4Ok:0
diff --git a/shell/ash_test/ash-heredoc/heredoc_empty2.tests b/shell/ash_test/ash-heredoc/heredoc_empty2.tests
new file mode 100755
index 000000000..20fc35fe9
--- /dev/null
+++ b/shell/ash_test/ash-heredoc/heredoc_empty2.tests
@@ -0,0 +1,14 @@
1unset a
2
3# Heredoc with empty delimiter
4cat <<- ""
5 OK1
6
7echo Ok:$?
8
9# Heredoc with empty delimiter
10cat <<- ""
11 OK2
12
13
14echo Ok:$?
diff --git a/shell/ash_test/ash-misc/func_prio_over_builtins.right b/shell/ash_test/ash-misc/func_prio_over_builtins.right
new file mode 100644
index 000000000..54e56dff4
--- /dev/null
+++ b/shell/ash_test/ash-misc/func_prio_over_builtins.right
@@ -0,0 +1,5 @@
1YES
2YES
3YES
4YES
5Ok:YES
diff --git a/shell/ash_test/ash-misc/func_prio_over_builtins.tests b/shell/ash_test/ash-misc/func_prio_over_builtins.tests
new file mode 100755
index 000000000..4f71bfda0
--- /dev/null
+++ b/shell/ash_test/ash-misc/func_prio_over_builtins.tests
@@ -0,0 +1,5 @@
1true() { echo YES >&2; }
2true
3true | true
4(true)
5echo Ok:`true 2>&1`
diff --git a/shell/ash_test/ash-misc/func6.right b/shell/ash_test/ash-misc/func_return1.right
index 0ebd8e5a3..0ebd8e5a3 100644
--- a/shell/ash_test/ash-misc/func6.right
+++ b/shell/ash_test/ash-misc/func_return1.right
diff --git a/shell/ash_test/ash-misc/func6.tests b/shell/ash_test/ash-misc/func_return1.tests
index 029c3e85e..029c3e85e 100755
--- a/shell/ash_test/ash-misc/func6.tests
+++ b/shell/ash_test/ash-misc/func_return1.tests
diff --git a/shell/hush_test/hush-misc/func6.right b/shell/ash_test/ash-misc/func_return2.right
index 0ebd8e5a3..0ebd8e5a3 100644
--- a/shell/hush_test/hush-misc/func6.right
+++ b/shell/ash_test/ash-misc/func_return2.right
diff --git a/shell/ash_test/ash-misc/func_return2.tests b/shell/ash_test/ash-misc/func_return2.tests
new file mode 100755
index 000000000..a049dd180
--- /dev/null
+++ b/shell/ash_test/ash-misc/func_return2.tests
@@ -0,0 +1,6 @@
1f1() { return 2; }
2f1
3echo Two:$?
4false
5true | f1
6echo Two:$?
diff --git a/shell/ash_test/ash-redir/redir_script.tests b/shell/ash_test/ash-redir/redir_script.tests
index ccc497d7b..740daa461 100755
--- a/shell/ash_test/ash-redir/redir_script.tests
+++ b/shell/ash_test/ash-redir/redir_script.tests
@@ -20,10 +20,15 @@ eval "find_fds $fds"
20 20
21# Shell should not lose that fd. Did it? 21# Shell should not lose that fd. Did it?
22find_fds 22find_fds
23test x"$fds1" = x"$fds" && { echo "Ok: script fd is not closed"; exit 0; } 23test x"$fds1" = x"$fds" \
24&& { echo "Ok: script fd is not closed"; exit 0; }
25
26# One legit way to handle it is to move script fd. For example, if we see that fd 10 moved to fd 11:
27test x"$fds1" = x" 10>&- 3>&-" && \
28test x"$fds" = x" 11>&- 3>&-" \
29&& { echo "Ok: script fd is not closed"; exit 0; }
24 30
25echo "Bug: script fd is closed" 31echo "Bug: script fd is closed"
26echo "fds1:$fds1" 32echo "fds1:$fds1"
27echo "fds2:$fds" 33echo "fds2:$fds"
28exit 1 34exit 1
29
diff --git a/shell/ash_test/ash-redir/redir_to_bad_fd255.right b/shell/ash_test/ash-redir/redir_to_bad_fd255.right
new file mode 100644
index 000000000..9c5e35b36
--- /dev/null
+++ b/shell/ash_test/ash-redir/redir_to_bad_fd255.right
@@ -0,0 +1,2 @@
1./redir_to_bad_fd255.tests: line 2: 255: Bad file descriptor
2OK
diff --git a/shell/ash_test/ash-redir/redir_to_bad_fd255.tests b/shell/ash_test/ash-redir/redir_to_bad_fd255.tests
new file mode 100755
index 000000000..2266af6da
--- /dev/null
+++ b/shell/ash_test/ash-redir/redir_to_bad_fd255.tests
@@ -0,0 +1,3 @@
1# ash uses fd 10 (usually) for reading the script
2echo LOST >&255
3echo OK
diff --git a/shell/ash_test/ash-redir/redir_to_bad_fd3.right b/shell/ash_test/ash-redir/redir_to_bad_fd3.right
new file mode 100644
index 000000000..895a4a0a6
--- /dev/null
+++ b/shell/ash_test/ash-redir/redir_to_bad_fd3.right
@@ -0,0 +1,2 @@
1./redir_to_bad_fd3.tests: line 2: 3: Bad file descriptor
2OK
diff --git a/shell/ash_test/ash-redir/redir_to_bad_fd3.tests b/shell/ash_test/ash-redir/redir_to_bad_fd3.tests
new file mode 100755
index 000000000..98c54cfc6
--- /dev/null
+++ b/shell/ash_test/ash-redir/redir_to_bad_fd3.tests
@@ -0,0 +1,3 @@
1# ash uses fd 10 (usually) for reading the script
2echo LOST >&3
3echo OK
diff --git a/shell/hush.c b/shell/hush.c
index d0225edb9..9f946d82f 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -1454,11 +1454,11 @@ static int fcntl_F_DUPFD(int fd, int avoid_fd)
1454 return newfd; 1454 return newfd;
1455} 1455}
1456 1456
1457static int xdup_and_close(int fd, int F_DUPFD_maybe_CLOEXEC, int avoid_fd) 1457static int xdup_CLOEXEC_and_close(int fd, int avoid_fd)
1458{ 1458{
1459 int newfd; 1459 int newfd;
1460 repeat: 1460 repeat:
1461 newfd = fcntl(fd, F_DUPFD_maybe_CLOEXEC, avoid_fd + 1); 1461 newfd = fcntl(fd, F_DUPFD_CLOEXEC, avoid_fd + 1);
1462 if (newfd < 0) { 1462 if (newfd < 0) {
1463 if (errno == EBUSY) 1463 if (errno == EBUSY)
1464 goto repeat; 1464 goto repeat;
@@ -1469,6 +1469,8 @@ static int xdup_and_close(int fd, int F_DUPFD_maybe_CLOEXEC, int avoid_fd)
1469 return fd; 1469 return fd;
1470 xfunc_die(); 1470 xfunc_die();
1471 } 1471 }
1472 if (F_DUPFD_CLOEXEC == F_DUPFD) /* if old libc (w/o F_DUPFD_CLOEXEC) */
1473 fcntl(newfd, F_SETFD, FD_CLOEXEC);
1472 close(fd); 1474 close(fd);
1473 return newfd; 1475 return newfd;
1474} 1476}
@@ -1507,7 +1509,7 @@ static int save_FILEs_on_redirect(int fd, int avoid_fd)
1507 while (fl) { 1509 while (fl) {
1508 if (fd == fl->fd) { 1510 if (fd == fl->fd) {
1509 /* We use it only on script files, they are all CLOEXEC */ 1511 /* We use it only on script files, they are all CLOEXEC */
1510 fl->fd = xdup_and_close(fd, F_DUPFD_CLOEXEC, avoid_fd); 1512 fl->fd = xdup_CLOEXEC_and_close(fd, avoid_fd);
1511 debug_printf_redir("redirect_fd %d: matches a script fd, moving it to %d\n", fd, fl->fd); 1513 debug_printf_redir("redirect_fd %d: matches a script fd, moving it to %d\n", fd, fl->fd);
1512 return 1; 1514 return 1;
1513 } 1515 }
@@ -1544,6 +1546,16 @@ static void close_all_FILE_list(void)
1544 } 1546 }
1545} 1547}
1546#endif 1548#endif
1549static int fd_in_FILEs(int fd)
1550{
1551 struct FILE_list *fl = G.FILE_list;
1552 while (fl) {
1553 if (fl->fd == fd)
1554 return 1;
1555 fl = fl->next;
1556 }
1557 return 0;
1558}
1547 1559
1548 1560
1549/* Helpers for setting new $n and restoring them back 1561/* Helpers for setting new $n and restoring them back
@@ -4001,24 +4013,34 @@ static char *fetch_till_str(o_string *as_string,
4001 ch = i_getch(input); 4013 ch = i_getch(input);
4002 if (ch != EOF) 4014 if (ch != EOF)
4003 nommu_addchr(as_string, ch); 4015 nommu_addchr(as_string, ch);
4004 if ((ch == '\n' || ch == EOF) 4016 if (ch == '\n' || ch == EOF) {
4005 && ((heredoc_flags & HEREDOC_QUOTED) || prev != '\\') 4017 check_heredoc_end:
4006 ) { 4018 if ((heredoc_flags & HEREDOC_QUOTED) || prev != '\\') {
4007 if (strcmp(heredoc.data + past_EOL, word) == 0) { 4019 if (strcmp(heredoc.data + past_EOL, word) == 0) {
4008 heredoc.data[past_EOL] = '\0'; 4020 heredoc.data[past_EOL] = '\0';
4009 debug_printf_parse("parsed heredoc '%s'\n", heredoc.data); 4021 debug_printf_parse("parsed heredoc '%s'\n", heredoc.data);
4010 return heredoc.data; 4022 return heredoc.data;
4011 } 4023 }
4012 while (ch == '\n') { 4024 if (ch == '\n') {
4013 o_addchr(&heredoc, ch); 4025 /* This is a new line.
4014 prev = ch; 4026 * Remember position and backslash-escaping status.
4027 */
4028 o_addchr(&heredoc, ch);
4029 prev = ch;
4015 jump_in: 4030 jump_in:
4016 past_EOL = heredoc.length; 4031 past_EOL = heredoc.length;
4017 do { 4032 /* Get 1st char of next line, possibly skipping leading tabs */
4018 ch = i_getch(input); 4033 do {
4019 if (ch != EOF) 4034 ch = i_getch(input);
4020 nommu_addchr(as_string, ch); 4035 if (ch != EOF)
4021 } while ((heredoc_flags & HEREDOC_SKIPTABS) && ch == '\t'); 4036 nommu_addchr(as_string, ch);
4037 } while ((heredoc_flags & HEREDOC_SKIPTABS) && ch == '\t');
4038 /* If this immediately ended the line,
4039 * go back to end-of-line checks.
4040 */
4041 if (ch == '\n')
4042 goto check_heredoc_end;
4043 }
4022 } 4044 }
4023 } 4045 }
4024 if (ch == EOF) { 4046 if (ch == EOF) {
@@ -6674,9 +6696,9 @@ static struct squirrel *append_squirrel(struct squirrel *sq, int i, int orig, in
6674static struct squirrel *add_squirrel(struct squirrel *sq, int fd, int avoid_fd) 6696static struct squirrel *add_squirrel(struct squirrel *sq, int fd, int avoid_fd)
6675{ 6697{
6676 int moved_to; 6698 int moved_to;
6677 int i = 0; 6699 int i;
6678 6700
6679 if (sq) while (sq[i].orig_fd >= 0) { 6701 if (sq) for (i = 0; sq[i].orig_fd >= 0; i++) {
6680 /* If we collide with an already moved fd... */ 6702 /* If we collide with an already moved fd... */
6681 if (fd == sq[i].moved_to) { 6703 if (fd == sq[i].moved_to) {
6682 sq[i].moved_to = fcntl_F_DUPFD(sq[i].moved_to, avoid_fd); 6704 sq[i].moved_to = fcntl_F_DUPFD(sq[i].moved_to, avoid_fd);
@@ -6690,7 +6712,6 @@ static struct squirrel *add_squirrel(struct squirrel *sq, int fd, int avoid_fd)
6690 debug_printf_redir("redirect_fd %d: already moved\n", fd); 6712 debug_printf_redir("redirect_fd %d: already moved\n", fd);
6691 return sq; 6713 return sq;
6692 } 6714 }
6693 i++;
6694 } 6715 }
6695 6716
6696 /* If this fd is open, we move and remember it; if it's closed, moved_to = -1 */ 6717 /* If this fd is open, we move and remember it; if it's closed, moved_to = -1 */
@@ -6701,18 +6722,41 @@ static struct squirrel *add_squirrel(struct squirrel *sq, int fd, int avoid_fd)
6701 return append_squirrel(sq, i, fd, moved_to); 6722 return append_squirrel(sq, i, fd, moved_to);
6702} 6723}
6703 6724
6725static struct squirrel *add_squirrel_closed(struct squirrel *sq, int fd)
6726{
6727 int i;
6728
6729 if (sq) for (i = 0; sq[i].orig_fd >= 0; i++) {
6730 /* If we collide with an already moved fd... */
6731 if (fd == sq[i].orig_fd) {
6732 /* Examples:
6733 * "echo 3>FILE 3>&- 3>FILE"
6734 * "echo 3>&- 3>FILE"
6735 * No need for last redirect to insert
6736 * another "need to close 3" indicator.
6737 */
6738 debug_printf_redir("redirect_fd %d: already moved or closed\n", fd);
6739 return sq;
6740 }
6741 }
6742
6743 debug_printf_redir("redirect_fd %d: previous fd was closed\n", fd);
6744 return append_squirrel(sq, i, fd, -1);
6745}
6746
6704/* fd: redirect wants this fd to be used (e.g. 3>file). 6747/* fd: redirect wants this fd to be used (e.g. 3>file).
6705 * Move all conflicting internally used fds, 6748 * Move all conflicting internally used fds,
6706 * and remember them so that we can restore them later. 6749 * and remember them so that we can restore them later.
6707 */ 6750 */
6708static int save_fds_on_redirect(int fd, int avoid_fd, struct squirrel **sqp) 6751static int save_fd_on_redirect(int fd, int avoid_fd, struct squirrel **sqp)
6709{ 6752{
6710 if (avoid_fd < 9) /* the important case here is that it can be -1 */ 6753 if (avoid_fd < 9) /* the important case here is that it can be -1 */
6711 avoid_fd = 9; 6754 avoid_fd = 9;
6712 6755
6713#if ENABLE_HUSH_INTERACTIVE 6756#if ENABLE_HUSH_INTERACTIVE
6714 if (fd != 0 && fd == G.interactive_fd) { 6757 if (fd == G.interactive_fd) {
6715 G.interactive_fd = xdup_and_close(G.interactive_fd, F_DUPFD_CLOEXEC, avoid_fd); 6758 /* Testcase: "ls -l /proc/$$/fd 255>&-" should work */
6759 G.interactive_fd = xdup_CLOEXEC_and_close(G.interactive_fd, avoid_fd);
6716 debug_printf_redir("redirect_fd %d: matches interactive_fd, moving it to %d\n", fd, G.interactive_fd); 6760 debug_printf_redir("redirect_fd %d: matches interactive_fd, moving it to %d\n", fd, G.interactive_fd);
6717 return 1; /* "we closed fd" */ 6761 return 1; /* "we closed fd" */
6718 } 6762 }
@@ -6738,10 +6782,9 @@ static int save_fds_on_redirect(int fd, int avoid_fd, struct squirrel **sqp)
6738 6782
6739static void restore_redirects(struct squirrel *sq) 6783static void restore_redirects(struct squirrel *sq)
6740{ 6784{
6741
6742 if (sq) { 6785 if (sq) {
6743 int i = 0; 6786 int i;
6744 while (sq[i].orig_fd >= 0) { 6787 for (i = 0; sq[i].orig_fd >= 0; i++) {
6745 if (sq[i].moved_to >= 0) { 6788 if (sq[i].moved_to >= 0) {
6746 /* We simply die on error */ 6789 /* We simply die on error */
6747 debug_printf_redir("restoring redirected fd from %d to %d\n", sq[i].moved_to, sq[i].orig_fd); 6790 debug_printf_redir("restoring redirected fd from %d to %d\n", sq[i].moved_to, sq[i].orig_fd);
@@ -6751,7 +6794,6 @@ static void restore_redirects(struct squirrel *sq)
6751 debug_printf_redir("restoring redirected fd %d: closing it\n", sq[i].orig_fd); 6794 debug_printf_redir("restoring redirected fd %d: closing it\n", sq[i].orig_fd);
6752 close(sq[i].orig_fd); 6795 close(sq[i].orig_fd);
6753 } 6796 }
6754 i++;
6755 } 6797 }
6756 free(sq); 6798 free(sq);
6757 } 6799 }
@@ -6761,17 +6803,47 @@ static void restore_redirects(struct squirrel *sq)
6761 restore_redirected_FILEs(); 6803 restore_redirected_FILEs();
6762} 6804}
6763 6805
6806#if ENABLE_FEATURE_SH_STANDALONE && BB_MMU
6807static void close_saved_fds_and_FILE_fds(void)
6808{
6809 if (G_interactive_fd)
6810 close(G_interactive_fd);
6811 close_all_FILE_list();
6812}
6813#endif
6814
6815static int internally_opened_fd(int fd, struct squirrel *sq)
6816{
6817 int i;
6818
6819#if ENABLE_HUSH_INTERACTIVE
6820 if (fd == G.interactive_fd)
6821 return 1;
6822#endif
6823 /* If this one of script's fds? */
6824 if (fd_in_FILEs(fd))
6825 return 1;
6826
6827 if (sq) for (i = 0; sq[i].orig_fd >= 0; i++) {
6828 if (fd == sq[i].moved_to)
6829 return 1;
6830 }
6831 return 0;
6832}
6833
6764/* squirrel != NULL means we squirrel away copies of stdin, stdout, 6834/* squirrel != NULL means we squirrel away copies of stdin, stdout,
6765 * and stderr if they are redirected. */ 6835 * and stderr if they are redirected. */
6766static int setup_redirects(struct command *prog, struct squirrel **sqp) 6836static int setup_redirects(struct command *prog, struct squirrel **sqp)
6767{ 6837{
6768 int openfd, mode;
6769 struct redir_struct *redir; 6838 struct redir_struct *redir;
6770 6839
6771 for (redir = prog->redirects; redir; redir = redir->next) { 6840 for (redir = prog->redirects; redir; redir = redir->next) {
6841 int newfd;
6842 int closed;
6843
6772 if (redir->rd_type == REDIRECT_HEREDOC2) { 6844 if (redir->rd_type == REDIRECT_HEREDOC2) {
6773 /* "rd_fd<<HERE" case */ 6845 /* "rd_fd<<HERE" case */
6774 save_fds_on_redirect(redir->rd_fd, /*avoid:*/ 0, sqp); 6846 save_fd_on_redirect(redir->rd_fd, /*avoid:*/ 0, sqp);
6775 /* for REDIRECT_HEREDOC2, rd_filename holds _contents_ 6847 /* for REDIRECT_HEREDOC2, rd_filename holds _contents_
6776 * of the heredoc */ 6848 * of the heredoc */
6777 debug_printf_parse("set heredoc '%s'\n", 6849 debug_printf_parse("set heredoc '%s'\n",
@@ -6783,6 +6855,8 @@ static int setup_redirects(struct command *prog, struct squirrel **sqp)
6783 if (redir->rd_dup == REDIRFD_TO_FILE) { 6855 if (redir->rd_dup == REDIRFD_TO_FILE) {
6784 /* "rd_fd<*>file" case (<*> is <,>,>>,<>) */ 6856 /* "rd_fd<*>file" case (<*> is <,>,>>,<>) */
6785 char *p; 6857 char *p;
6858 int mode;
6859
6786 if (redir->rd_filename == NULL) { 6860 if (redir->rd_filename == NULL) {
6787 /* 6861 /*
6788 * Examples: 6862 * Examples:
@@ -6794,9 +6868,9 @@ static int setup_redirects(struct command *prog, struct squirrel **sqp)
6794 } 6868 }
6795 mode = redir_table[redir->rd_type].mode; 6869 mode = redir_table[redir->rd_type].mode;
6796 p = expand_string_to_string(redir->rd_filename, /*unbackslash:*/ 1); 6870 p = expand_string_to_string(redir->rd_filename, /*unbackslash:*/ 1);
6797 openfd = open_or_warn(p, mode); 6871 newfd = open_or_warn(p, mode);
6798 free(p); 6872 free(p);
6799 if (openfd < 0) { 6873 if (newfd < 0) {
6800 /* Error message from open_or_warn can be lost 6874 /* Error message from open_or_warn can be lost
6801 * if stderr has been redirected, but bash 6875 * if stderr has been redirected, but bash
6802 * and ash both lose it as well 6876 * and ash both lose it as well
@@ -6804,40 +6878,52 @@ static int setup_redirects(struct command *prog, struct squirrel **sqp)
6804 */ 6878 */
6805 return 1; 6879 return 1;
6806 } 6880 }
6807 if (openfd == redir->rd_fd && sqp) { 6881 if (newfd == redir->rd_fd && sqp) {
6808 /* open() gave us precisely the fd we wanted. 6882 /* open() gave us precisely the fd we wanted.
6809 * This means that this fd was not busy 6883 * This means that this fd was not busy
6810 * (not opened to anywhere). 6884 * (not opened to anywhere).
6811 * Remember to close it on restore: 6885 * Remember to close it on restore:
6812 */ 6886 */
6813 struct squirrel *sq = *sqp; 6887 *sqp = add_squirrel_closed(*sqp, newfd);
6814 int i = 0; 6888 debug_printf_redir("redir to previously closed fd %d\n", newfd);
6815 if (sq) while (sq[i].orig_fd >= 0)
6816 i++;
6817 *sqp = append_squirrel(sq, i, openfd, -1); /* -1 = "it was closed" */
6818 debug_printf_redir("redir to previously closed fd %d\n", openfd);
6819 } 6889 }
6820 } else { 6890 } else {
6821 /* "rd_fd<*>rd_dup" or "rd_fd<*>-" cases */ 6891 /* "rd_fd>&rd_dup" or "rd_fd>&-" case */
6822 openfd = redir->rd_dup; 6892 newfd = redir->rd_dup;
6823 } 6893 }
6824 6894
6825 if (openfd != redir->rd_fd) { 6895 if (newfd == redir->rd_fd)
6826 int closed = save_fds_on_redirect(redir->rd_fd, /*avoid:*/ openfd, sqp); 6896 continue;
6827 if (openfd == REDIRFD_CLOSE) { 6897
6828 /* "rd_fd >&-" means "close me" */ 6898 /* if "N>FILE": move newfd to redir->rd_fd */
6829 if (!closed) { 6899 /* if "N>&M": dup newfd to redir->rd_fd */
6830 /* ^^^ optimization: saving may already 6900 /* if "N>&-": close redir->rd_fd (newfd is REDIRFD_CLOSE) */
6831 * have closed it. If not... */ 6901
6832 close(redir->rd_fd); 6902 closed = save_fd_on_redirect(redir->rd_fd, /*avoid:*/ newfd, sqp);
6833 } 6903 if (newfd == REDIRFD_CLOSE) {
6834 } else { 6904 /* "N>&-" means "close me" */
6835 xdup2(openfd, redir->rd_fd); 6905 if (!closed) {
6836 if (redir->rd_dup == REDIRFD_TO_FILE) 6906 /* ^^^ optimization: saving may already
6837 /* "rd_fd > FILE" */ 6907 * have closed it. If not... */
6838 close(openfd); 6908 close(redir->rd_fd);
6839 /* else: "rd_fd > rd_dup" */ 6909 }
6910 /* Sometimes we do another close on restore, getting EBADF.
6911 * Consider "echo 3>FILE 3>&-"
6912 * first redirect remembers "need to close 3",
6913 * and second redirect closes 3! Restore code then closes 3 again.
6914 */
6915 } else {
6916 /* if newfd is a script fd or saved fd, simulate EBADF */
6917 if (internally_opened_fd(newfd, sqp ? *sqp : NULL)) {
6918 //errno = EBADF;
6919 //bb_perror_msg_and_die("can't duplicate file descriptor");
6920 newfd = -1; /* same effect as code above */
6840 } 6921 }
6922 xdup2(newfd, redir->rd_fd);
6923 if (redir->rd_dup == REDIRFD_TO_FILE)
6924 /* "rd_fd > FILE" */
6925 close(newfd);
6926 /* else: "rd_fd > rd_dup" */
6841 } 6927 }
6842 } 6928 }
6843 return 0; 6929 return 0;
@@ -7004,11 +7090,34 @@ static void exec_function(char ***to_free,
7004 argv[0] = G.global_argv[0]; 7090 argv[0] = G.global_argv[0];
7005 G.global_argv = argv; 7091 G.global_argv = argv;
7006 G.global_argc = n = 1 + string_array_len(argv + 1); 7092 G.global_argc = n = 1 + string_array_len(argv + 1);
7093
7094// Example when we are here: "cmd | func"
7095// func will run with saved-redirect fds open.
7096// $ f() { echo /proc/self/fd/*; }
7097// $ true | f
7098// /proc/self/fd/0 /proc/self/fd/1 /proc/self/fd/2 /proc/self/fd/255 /proc/self/fd/3
7099// stdio^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ G_interactive_fd^ DIR fd for glob
7100// Same in script:
7101// $ . ./SCRIPT
7102// /proc/self/fd/0 /proc/self/fd/1 /proc/self/fd/2 /proc/self/fd/255 /proc/self/fd/3 /proc/self/fd/4
7103// stdio^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ G_interactive_fd^ opened ./SCRIPT DIR fd for glob
7104// They are CLOEXEC so external programs won't see them, but
7105// for "more correctness" we might want to close those extra fds here:
7106//? close_saved_fds_and_FILE_fds();
7107
7108 /* "we are in function, ok to use return" */
7109 G_flag_return_in_progress = -1;
7110 IF_HUSH_LOCAL(G.func_nest_level++;)
7111
7007 /* On MMU, funcp->body is always non-NULL */ 7112 /* On MMU, funcp->body is always non-NULL */
7008 n = run_list(funcp->body); 7113 n = run_list(funcp->body);
7009 fflush_all(); 7114 fflush_all();
7010 _exit(n); 7115 _exit(n);
7011# else 7116# else
7117//? close_saved_fds_and_FILE_fds();
7118
7119//TODO: check whether "true | func_with_return" works
7120
7012 re_execute_shell(to_free, 7121 re_execute_shell(to_free,
7013 funcp->body_as_string, 7122 funcp->body_as_string,
7014 G.global_argv[0], 7123 G.global_argv[0],
@@ -7028,9 +7137,7 @@ static int run_function(const struct function *funcp, char **argv)
7028 /* "we are in function, ok to use return" */ 7137 /* "we are in function, ok to use return" */
7029 sv_flg = G_flag_return_in_progress; 7138 sv_flg = G_flag_return_in_progress;
7030 G_flag_return_in_progress = -1; 7139 G_flag_return_in_progress = -1;
7031# if ENABLE_HUSH_LOCAL 7140 IF_HUSH_LOCAL(G.func_nest_level++;)
7032 G.func_nest_level++;
7033# endif
7034 7141
7035 /* On MMU, funcp->body is always non-NULL */ 7142 /* On MMU, funcp->body is always non-NULL */
7036# if !BB_MMU 7143# if !BB_MMU
@@ -7094,6 +7201,7 @@ static void exec_builtin(char ***to_free,
7094#if BB_MMU 7201#if BB_MMU
7095 int rcode; 7202 int rcode;
7096 fflush_all(); 7203 fflush_all();
7204//? close_saved_fds_and_FILE_fds();
7097 rcode = x->b_function(argv); 7205 rcode = x->b_function(argv);
7098 fflush_all(); 7206 fflush_all();
7099 _exit(rcode); 7207 _exit(rcode);
@@ -7215,6 +7323,16 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
7215 goto skip; 7323 goto skip;
7216#endif 7324#endif
7217 7325
7326#if ENABLE_HUSH_FUNCTIONS
7327 /* Check if the command matches any functions (this goes before bltins) */
7328 {
7329 const struct function *funcp = find_function(argv[0]);
7330 if (funcp) {
7331 exec_function(&nommu_save->argv_from_re_execing, funcp, argv);
7332 }
7333 }
7334#endif
7335
7218 /* Check if the command matches any of the builtins. 7336 /* Check if the command matches any of the builtins.
7219 * Depending on context, this might be redundant. But it's 7337 * Depending on context, this might be redundant. But it's
7220 * easier to waste a few CPU cycles than it is to figure out 7338 * easier to waste a few CPU cycles than it is to figure out
@@ -7231,15 +7349,6 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
7231 exec_builtin(&nommu_save->argv_from_re_execing, x, argv); 7349 exec_builtin(&nommu_save->argv_from_re_execing, x, argv);
7232 } 7350 }
7233 } 7351 }
7234#if ENABLE_HUSH_FUNCTIONS
7235 /* Check if the command matches any functions */
7236 {
7237 const struct function *funcp = find_function(argv[0]);
7238 if (funcp) {
7239 exec_function(&nommu_save->argv_from_re_execing, funcp, argv);
7240 }
7241 }
7242#endif
7243 7352
7244#if ENABLE_FEATURE_SH_STANDALONE 7353#if ENABLE_FEATURE_SH_STANDALONE
7245 /* Check if the command matches any busybox applets */ 7354 /* Check if the command matches any busybox applets */
@@ -7248,8 +7357,12 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
7248 if (a >= 0) { 7357 if (a >= 0) {
7249# if BB_MMU /* see above why on NOMMU it is not allowed */ 7358# if BB_MMU /* see above why on NOMMU it is not allowed */
7250 if (APPLET_IS_NOEXEC(a)) { 7359 if (APPLET_IS_NOEXEC(a)) {
7251 /* Do not leak open fds from opened script files etc */ 7360 /* Do not leak open fds from opened script files etc.
7252 close_all_FILE_list(); 7361 * Testcase: interactive "ls -l /proc/self/fd"
7362 * should not show tty fd open.
7363 */
7364 close_saved_fds_and_FILE_fds();
7365//FIXME: should also close saved redir fds
7253 debug_printf_exec("running applet '%s'\n", argv[0]); 7366 debug_printf_exec("running applet '%s'\n", argv[0]);
7254 run_applet_no_and_exit(a, argv[0], argv); 7367 run_applet_no_and_exit(a, argv[0], argv);
7255 } 7368 }
@@ -7886,12 +7999,13 @@ static NOINLINE int run_pipe(struct pipe *pi)
7886 return G.last_exitcode; 7999 return G.last_exitcode;
7887 } 8000 }
7888 8001
7889 x = find_builtin(argv_expanded[0]);
7890#if ENABLE_HUSH_FUNCTIONS 8002#if ENABLE_HUSH_FUNCTIONS
7891 funcp = NULL; 8003 /* Check if argv[0] matches any functions (this goes before bltins) */
7892 if (!x) 8004 funcp = find_function(argv_expanded[0]);
7893 funcp = find_function(argv_expanded[0]);
7894#endif 8005#endif
8006 x = NULL;
8007 if (!funcp)
8008 x = find_builtin(argv_expanded[0]);
7895 if (x || funcp) { 8009 if (x || funcp) {
7896 if (!funcp) { 8010 if (!funcp) {
7897 if (x->b_function == builtin_exec && argv_expanded[1] == NULL) { 8011 if (x->b_function == builtin_exec && argv_expanded[1] == NULL) {
@@ -9158,6 +9272,14 @@ static int FAST_FUNC builtin_exec(char **argv)
9158 if (G_saved_tty_pgrp && getpid() == G.root_pid) 9272 if (G_saved_tty_pgrp && getpid() == G.root_pid)
9159 tcsetpgrp(G_interactive_fd, G_saved_tty_pgrp); 9273 tcsetpgrp(G_interactive_fd, G_saved_tty_pgrp);
9160 9274
9275 /* Saved-redirect fds, script fds and G_interactive_fd are still
9276 * open here. However, they are all CLOEXEC, and execv below
9277 * closes them. Try interactive "exec ls -l /proc/self/fd",
9278 * it should show no extra open fds in the "ls" process.
9279 * If we'd try to run builtins/NOEXECs, this would need improving.
9280 */
9281 //close_saved_fds_and_FILE_fds();
9282
9161 /* TODO: if exec fails, bash does NOT exit! We do. 9283 /* TODO: if exec fails, bash does NOT exit! We do.
9162 * We'll need to undo trap cleanup (it's inside execvp_or_die) 9284 * We'll need to undo trap cleanup (it's inside execvp_or_die)
9163 * and tcsetpgrp, and this is inherently racy. 9285 * and tcsetpgrp, and this is inherently racy.
diff --git a/shell/hush_test/hush-heredoc/heredoc_empty2.right b/shell/hush_test/hush-heredoc/heredoc_empty2.right
new file mode 100644
index 000000000..e32c6ea58
--- /dev/null
+++ b/shell/hush_test/hush-heredoc/heredoc_empty2.right
@@ -0,0 +1,4 @@
1OK1
2Ok:0
3OK2
4Ok:0
diff --git a/shell/hush_test/hush-heredoc/heredoc_empty2.tests b/shell/hush_test/hush-heredoc/heredoc_empty2.tests
new file mode 100755
index 000000000..20fc35fe9
--- /dev/null
+++ b/shell/hush_test/hush-heredoc/heredoc_empty2.tests
@@ -0,0 +1,14 @@
1unset a
2
3# Heredoc with empty delimiter
4cat <<- ""
5 OK1
6
7echo Ok:$?
8
9# Heredoc with empty delimiter
10cat <<- ""
11 OK2
12
13
14echo Ok:$?
diff --git a/shell/hush_test/hush-misc/func_prio_over_builtins.right b/shell/hush_test/hush-misc/func_prio_over_builtins.right
new file mode 100644
index 000000000..54e56dff4
--- /dev/null
+++ b/shell/hush_test/hush-misc/func_prio_over_builtins.right
@@ -0,0 +1,5 @@
1YES
2YES
3YES
4YES
5Ok:YES
diff --git a/shell/hush_test/hush-misc/func_prio_over_builtins.tests b/shell/hush_test/hush-misc/func_prio_over_builtins.tests
new file mode 100755
index 000000000..4f71bfda0
--- /dev/null
+++ b/shell/hush_test/hush-misc/func_prio_over_builtins.tests
@@ -0,0 +1,5 @@
1true() { echo YES >&2; }
2true
3true | true
4(true)
5echo Ok:`true 2>&1`
diff --git a/shell/hush_test/hush-misc/func_return1.right b/shell/hush_test/hush-misc/func_return1.right
new file mode 100644
index 000000000..0ebd8e5a3
--- /dev/null
+++ b/shell/hush_test/hush-misc/func_return1.right
@@ -0,0 +1,2 @@
1Two:2
2Two:2
diff --git a/shell/hush_test/hush-misc/func6.tests b/shell/hush_test/hush-misc/func_return1.tests
index 029c3e85e..029c3e85e 100755
--- a/shell/hush_test/hush-misc/func6.tests
+++ b/shell/hush_test/hush-misc/func_return1.tests
diff --git a/shell/hush_test/hush-misc/func_return2.right b/shell/hush_test/hush-misc/func_return2.right
new file mode 100644
index 000000000..0ebd8e5a3
--- /dev/null
+++ b/shell/hush_test/hush-misc/func_return2.right
@@ -0,0 +1,2 @@
1Two:2
2Two:2
diff --git a/shell/hush_test/hush-misc/func_return2.tests b/shell/hush_test/hush-misc/func_return2.tests
new file mode 100755
index 000000000..a049dd180
--- /dev/null
+++ b/shell/hush_test/hush-misc/func_return2.tests
@@ -0,0 +1,6 @@
1f1() { return 2; }
2f1
3echo Two:$?
4false
5true | f1
6echo Two:$?
diff --git a/shell/hush_test/hush-redir/redir_script.tests b/shell/hush_test/hush-redir/redir_script.tests
index ccc497d7b..740daa461 100755
--- a/shell/hush_test/hush-redir/redir_script.tests
+++ b/shell/hush_test/hush-redir/redir_script.tests
@@ -20,10 +20,15 @@ eval "find_fds $fds"
20 20
21# Shell should not lose that fd. Did it? 21# Shell should not lose that fd. Did it?
22find_fds 22find_fds
23test x"$fds1" = x"$fds" && { echo "Ok: script fd is not closed"; exit 0; } 23test x"$fds1" = x"$fds" \
24&& { echo "Ok: script fd is not closed"; exit 0; }
25
26# One legit way to handle it is to move script fd. For example, if we see that fd 10 moved to fd 11:
27test x"$fds1" = x" 10>&- 3>&-" && \
28test x"$fds" = x" 11>&- 3>&-" \
29&& { echo "Ok: script fd is not closed"; exit 0; }
24 30
25echo "Bug: script fd is closed" 31echo "Bug: script fd is closed"
26echo "fds1:$fds1" 32echo "fds1:$fds1"
27echo "fds2:$fds" 33echo "fds2:$fds"
28exit 1 34exit 1
29
diff --git a/shell/hush_test/hush-redir/redir_to_bad_fd255.right b/shell/hush_test/hush-redir/redir_to_bad_fd255.right
new file mode 100644
index 000000000..936911ce5
--- /dev/null
+++ b/shell/hush_test/hush-redir/redir_to_bad_fd255.right
@@ -0,0 +1 @@
hush: can't duplicate file descriptor: Bad file descriptor
diff --git a/shell/hush_test/hush-redir/redir_to_bad_fd255.tests b/shell/hush_test/hush-redir/redir_to_bad_fd255.tests
new file mode 100755
index 000000000..2266af6da
--- /dev/null
+++ b/shell/hush_test/hush-redir/redir_to_bad_fd255.tests
@@ -0,0 +1,3 @@
1# ash uses fd 10 (usually) for reading the script
2echo LOST >&255
3echo OK
diff --git a/shell/hush_test/hush-redir/redir_to_bad_fd3.right b/shell/hush_test/hush-redir/redir_to_bad_fd3.right
new file mode 100644
index 000000000..936911ce5
--- /dev/null
+++ b/shell/hush_test/hush-redir/redir_to_bad_fd3.right
@@ -0,0 +1 @@
hush: can't duplicate file descriptor: Bad file descriptor
diff --git a/shell/hush_test/hush-redir/redir_to_bad_fd3.tests b/shell/hush_test/hush-redir/redir_to_bad_fd3.tests
new file mode 100755
index 000000000..98c54cfc6
--- /dev/null
+++ b/shell/hush_test/hush-redir/redir_to_bad_fd3.tests
@@ -0,0 +1,3 @@
1# ash uses fd 10 (usually) for reading the script
2echo LOST >&3
3echo OK