ash: fix a bug where redirection fds were not closed afterwards.
optimize close+fcntl(DUPFD) into dup2. add testsuites.
function old new delta
copyfd 47 68 +21
argstr 1311 1298 -13
popredir 148 131 -17
redirect 1139 1107 -32
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 1/3 up/down: 21/-62) Total: -41 bytes
diff --git a/shell/ash.c b/shell/ash.c
index 22575ff..ec3bd09 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -4846,12 +4846,21 @@
* if the source file descriptor is closed, EMPTY if there are no unused
* file descriptors left.
*/
+/* 0x800..00: bit to set in "to" to request dup2 instead of fcntl(F_DUPFD).
+ * old code was doing close(to) prior to copyfd() to achieve the same */
+#define COPYFD_EXACT ((int)~INT_MAX)
static int
copyfd(int from, int to)
{
int newfd;
- newfd = fcntl(from, F_DUPFD, to);
+ if (to & COPYFD_EXACT) {
+ to &= ~COPYFD_EXACT;
+ /*if (from != to)*/
+ newfd = dup2(from, to);
+ } else {
+ newfd = fcntl(from, F_DUPFD, to);
+ }
if (newfd < 0) {
if (errno == EMFILE)
return EMPTY;
@@ -4947,11 +4956,7 @@
/* Descriptor wasn't open before redirect.
* Mark it for close in the future */
if (need_to_remember(sv, fd)) {
- if (fd == 2)
- copied_fd2 = CLOSED; /// do we need this?
- sv->two_fd[sv_pos].orig = fd;
- sv->two_fd[sv_pos].copy = CLOSED;
- sv_pos++;
+ goto remember_to_close;
}
continue;
}
@@ -4959,6 +4964,10 @@
if (need_to_remember(sv, fd)) {
/* Copy old descriptor */
i = fcntl(fd, F_DUPFD, 10);
+/* You'd expect copy to be CLOEXECed. Currently these extra "saved" fds
+ * are closed in popredir() in the child, preventing them from leaking
+ * into child. (popredir() also cleans up the mess in case of failures)
+ */
if (i == -1) {
i = errno;
if (i != EBADF) {
@@ -4969,31 +4978,25 @@
ash_msg_and_raise_error("%d: %m", fd);
/* NOTREACHED */
}
- /* EBADF: it is not open - ok */
- } else {
- /* fd is open, save its copy */
-/* You'd expect copy to be CLOEXECed. Currently these extra "saved" fds
- * are closed in popredir() in the child, preventing them from leaking
- * into child. (popredir() also cleans up the mess in case of failures)
- */
- if (fd == 2)
- copied_fd2 = i;
- sv->two_fd[sv_pos].orig = fd;
- sv->two_fd[sv_pos].copy = i;
- sv_pos++;
- close(fd);
- }
- } else {
- close(fd);
+ /* EBADF: it is not open - good, remember to close it */
+ remember_to_close:
+ i = CLOSED;
+ } /* else: fd is open, save its copy */
+ if (fd == 2)
+ copied_fd2 = i;
+ sv->two_fd[sv_pos].orig = fd;
+ sv->two_fd[sv_pos].copy = i;
+ sv_pos++;
}
- /* At this point fd is closed */
if (newfd < 0) {
/* NTOFD/NFROMFD: copy redir->ndup.dupfd to fd */
- if (redir->ndup.dupfd >= 0) { /* if not ">&-" */
- copyfd(redir->ndup.dupfd, fd);
+ if (redir->ndup.dupfd < 0) { /* "NN>&-" */
+ close(fd);
+ } else {
+ copyfd(redir->ndup.dupfd, fd | COPYFD_EXACT);
}
- } else { /* move newfd to fd */
- copyfd(newfd, fd);
+ } else if (fd != newfd) { /* move newfd to fd */
+ copyfd(newfd, fd | COPYFD_EXACT);
close(newfd);
}
} while ((redir = redir->nfile.next) != NULL);
@@ -5025,8 +5028,8 @@
}
if (rp->two_fd[i].copy != EMPTY) {
if (!drop) {
- close(fd);
- copyfd(rp->two_fd[i].copy, fd);
+ /*close(fd);*/
+ copyfd(rp->two_fd[i].copy, fd | COPYFD_EXACT);
}
close(rp->two_fd[i].copy);
}
@@ -5064,7 +5067,8 @@
struct jmploc jmploc;
SAVE_INT(saveint);
- err = setjmp(jmploc.loc) * 2;
+ /* "echo 9>/dev/null; echo >&9; echo result: $?" - result should be 1, not 2! */
+ err = setjmp(jmploc.loc); // huh?? was = setjmp(jmploc.loc) * 2;
if (!err) {
exception_handler = &jmploc;
redirect(redir, flags);
@@ -5443,8 +5447,8 @@
FORCE_INT_ON;
close(pip[0]);
if (pip[1] != 1) {
- close(1);
- copyfd(pip[1], 1);
+ /*close(1);*/
+ copyfd(pip[1], 1 | COPYFD_EXACT);
close(pip[1]);
}
eflag = 0;
@@ -10691,11 +10695,7 @@
len = out - (char *)stackblock();
out = stackblock();
if (eofmark == NULL) {
- if ((c == '>' || c == '<')
- && quotef == 0
- // && len <= 2 // THIS LIMITS fd to 1 char: N>file, but no NN>file!
- // && (*out == '\0' || isdigit(*out))
- ) {
+ if ((c == '>' || c == '<') && quotef == 0) {
int maxlen = 9 + 1; /* max 9 digit fd#: 999999999 */
char *np = out;
while (--maxlen && isdigit(*np))
diff --git a/shell/ash_test/ash-redir/redir2.right b/shell/ash_test/ash-redir/redir2.right
new file mode 100644
index 0000000..d86bac9
--- /dev/null
+++ b/shell/ash_test/ash-redir/redir2.right
@@ -0,0 +1 @@
+OK
diff --git a/shell/ash_test/ash-redir/redir2.tests b/shell/ash_test/ash-redir/redir2.tests
new file mode 100755
index 0000000..61ccea3
--- /dev/null
+++ b/shell/ash_test/ash-redir/redir2.tests
@@ -0,0 +1,5 @@
+# ash once couldn't redirect above fd#9
+exec 1>/dev/null
+(echo LOST1 >&22) 22>&1
+(echo LOST2 >&22) 22>&1
+(echo OK >&22) 22>&2
diff --git a/shell/ash_test/ash-redir/redir3.right b/shell/ash_test/ash-redir/redir3.right
new file mode 100644
index 0000000..fd641a8
--- /dev/null
+++ b/shell/ash_test/ash-redir/redir3.right
@@ -0,0 +1,3 @@
+TEST
+./redir3.tests: line 4: 9: Bad file descriptor
+Output to fd#9: 1
diff --git a/shell/ash_test/ash-redir/redir3.tests b/shell/ash_test/ash-redir/redir3.tests
new file mode 100755
index 0000000..f50a767
--- /dev/null
+++ b/shell/ash_test/ash-redir/redir3.tests
@@ -0,0 +1,5 @@
+# redirects to closed descriptors should not leave these descriptors"
+# open afterwards
+echo TEST 9>/dev/null
+echo MUST ERROR OUT >&9
+echo "Output to fd#9: $?"
diff --git a/shell/ash_test/run-all b/shell/ash_test/run-all
index b0a6d10..4d0f39a 100755
--- a/shell/ash_test/run-all
+++ b/shell/ash_test/run-all
@@ -3,8 +3,8 @@
TOPDIR=$PWD
test -x ash || {
- echo "No ./ash?! Perhaps you want to run 'ln -s ../../busybox ash'"
- exit
+ echo "No ./ash - creating a link to ../../busybox"
+ ln -s ../../busybox ash
}
test -x printenv || gcc -O2 -o printenv printenv.c || exit $?
test -x recho || gcc -O2 -o recho recho.c || exit $?