Skip to content

Commit 8e1e405

Browse files
committed
Fix [ -t1 ] inside a command substitution
Another non-forking subshell bug involving `[ -t 1 ]` being true when it should be false. Fixes #1079
1 parent 6cd0b41 commit 8e1e405

File tree

6 files changed

+79
-30
lines changed

6 files changed

+79
-30
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ None at this time.
4343

4444
## Notable fixes and improvements
4545

46+
- Doing `[ -t1 ]` inside a command substitution behaves correctly
47+
(issue #1079).
4648
- The project now passes its unit tests when built with malloc debugging
4749
enabled (i.e., `meson test --setup=malloc`).
4850
- Changes to the project are now validated by running unit tests on the Travis

src/cmd/ksh93/bltins/test.c

+18-13
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ struct test {
9292
};
9393

9494
static_fn char *nxtarg(struct test *, int);
95-
static_fn int eval_expr(struct test *, int);
96-
static_fn int eval_e3(struct test *);
95+
static_fn int eval_expr(Shell_t *shp, struct test *, int);
96+
static_fn int eval_e3(Shell_t *shp, struct test *);
9797

9898
static_fn int test_strmatch(Shell_t *shp, const char *str, const char *pat) {
9999
int match[2 * (MATCH_MAX + 1)], n;
@@ -223,7 +223,7 @@ int b_test(int argc, char *argv[], Shbltin_t *context) {
223223
default: { break; }
224224
}
225225
tdata.ac = argc;
226-
result = !eval_expr(&tdata, 0);
226+
result = !eval_expr(shp, &tdata, 0);
227227

228228
done:
229229
sh_popcontext(shp, &buff);
@@ -236,11 +236,11 @@ int b_test(int argc, char *argv[], Shbltin_t *context) {
236236
// Flag is 1 when in parenthesis.
237237
// Flag is 2 when evaluating -a.
238238
//
239-
static_fn int eval_expr(struct test *tp, int flag) {
239+
static_fn int eval_expr(Shell_t *shp, struct test *tp, int flag) {
240240
int r;
241241
char *p;
242242

243-
r = eval_e3(tp);
243+
r = eval_e3(shp, tp);
244244
while (tp->ap < tp->ac) {
245245
p = nxtarg(tp, 0);
246246
// Check for -o and -a.
@@ -254,10 +254,10 @@ static_fn int eval_expr(struct test *tp, int flag) {
254254
tp->ap--;
255255
break;
256256
}
257-
r |= eval_expr(tp, 3);
257+
r |= eval_expr(shp, tp, 3);
258258
continue;
259259
} else if (*p == 'a') {
260-
r &= eval_expr(tp, 2);
260+
r &= eval_expr(shp, tp, 2);
261261
continue;
262262
}
263263
}
@@ -280,15 +280,15 @@ static_fn char *nxtarg(struct test *tp, int mt) {
280280
return tp->av[tp->ap++];
281281
}
282282

283-
static_fn int eval_e3(struct test *tp) {
283+
static_fn int eval_e3(Shell_t *shp, struct test *tp) {
284284
char *arg, *cp;
285285
int op;
286286
char *binop;
287287

288288
arg = nxtarg(tp, 0);
289-
if (c_eq(arg, '!')) return !eval_e3(tp);
289+
if (c_eq(arg, '!')) return !eval_e3(shp, tp);
290290
if (c_eq(arg, '(')) {
291-
op = eval_expr(tp, 1);
291+
op = eval_expr(shp, tp, 1);
292292
cp = nxtarg(tp, 0);
293293
if (!cp || !c_eq(cp, ')')) {
294294
errormsg(SH_DICT, ERROR_exit(2), e_missing, "')'");
@@ -301,11 +301,14 @@ static_fn int eval_e3(struct test *tp) {
301301
if (c2_eq(arg, '-', 't')) {
302302
if (cp) {
303303
op = strtol(cp, &binop, 10);
304-
return *binop ? 0 : tty_check(op);
304+
if (*binop) return 0;
305+
if (shp->subshell && op == STDOUT_FILENO) return 0;
306+
return tty_check(op);
305307
}
306308
// Test -t with no arguments.
307309
tp->ap--;
308-
return tty_check(1);
310+
if (shp->subshell) return 0;
311+
return tty_check(STDOUT_FILENO);
309312
}
310313
if (*arg == '-' && arg[2] == 0) {
311314
op = arg[1];
@@ -441,7 +444,9 @@ int test_unop(Shell_t *shp, int op, const char *arg) {
441444
case 't': {
442445
char *last;
443446
op = strtol(arg, &last, 10);
444-
return *last ? 0 : tty_check(op);
447+
if (*last) return 0;
448+
if (shp->subshell && op == STDOUT_FILENO) return 0;
449+
return tty_check(op);
445450
}
446451
case 'v':
447452
case 'R': {

src/cmd/ksh93/tests/basic.sh

+22-17
Original file line numberDiff line numberDiff line change
@@ -421,28 +421,33 @@ expect=$'x\ny\nz'
421421
for tee in "$(whence tee)" $bin_tee
422422
do
423423
print xxx > $TEST_DIR/file
424-
$tee >(sleep 1;cat > $TEST_DIR/file) <<< "hello" > /dev/null
425-
[[ $(< $TEST_DIR/file) == hello ]] ||
426-
log_error "process substitution does not wait for >() to complete with $tee"
424+
$tee >(sleep 1; cat > $TEST_DIR/file) <<< "hello" > /dev/null
425+
actual=$(< $TEST_DIR/file)
426+
expect=hello
427+
[[ $actual == $expect ]] ||
428+
log_error "process substitution does not wait for >() to complete with $tee" "$expect" "$actual"
429+
427430
print yyy > $TEST_DIR/file2
428-
$tee >(cat > $TEST_DIR/file) >(sleep 1;cat > $TEST_DIR/file2) <<< "hello" > /dev/null
429-
[[ $(< $TEST_DIR/file2) == hello ]] ||
430-
log_error "process substitution does not wait for second of two >() to complete with $tee"
431+
$tee >(cat > $TEST_DIR/file) >(sleep 1; cat > $TEST_DIR/file2) <<< "hello" > /dev/null
432+
actual=$(< $TEST_DIR/file2)
433+
expect=hello
434+
[[ $actual == $expect ]] ||
435+
log_error "process substitution does not wait for second of two >() to complete with $tee" "$expect" "$actual"
436+
431437
print xxx > $TEST_DIR/file
432-
$tee >(sleep 1;cat > $TEST_DIR/file) >(cat > $TEST_DIR/file2) <<< "hello" > /dev/null
433-
[[ $(< $TEST_DIR/file) == hello ]] ||
434-
log_error "process substitution does not wait for first of two >() to complete with $tee"
438+
$tee >(sleep 1; cat > $TEST_DIR/file) >(cat > $TEST_DIR/file2) <<< "hello" > /dev/null
439+
actual=$(< $TEST_DIR/file)
440+
expect=hello
441+
[[ $actual == $expect ]] ||
442+
log_error "process substitution does not wait for first of two >() to complete with $tee" "$expect" "$actual"
435443
done
436444

437-
if [[ -d /dev/fd ]]
445+
if [[ $HAS_DEV_FD == yes ]]
438446
then
439-
if [[ $(print <(print foo) & sleep .5; kill $! 2>/dev/null) == /dev/fd/* ]]
440-
then
441-
expect='/dev/fd/+(\d) v=bam /dev/fd/+(\d)'
442-
actual=$( print <(print foo) v=bam <(print bar))
443-
[[ $actual == $expect ]] ||
444-
log_error 'assignments after command subst not treated as arguments' "$expect" "$actual"
445-
fi
447+
expect='/dev/fd/+(\d) v=bam /dev/fd/+(\d)'
448+
actual=$( print <(print foo) v=bam <(print bar))
449+
[[ $actual == $expect ]] ||
450+
log_error 'assignments after command substitution not treated as arguments' "$expect" "$actual"
446451
fi
447452

448453
# ========

src/cmd/ksh93/tests/test.exp

+22
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,28 @@ expect "\r\nPASS\r\n" {
1919
}
2020
expect_prompt
2121

22+
# ==========
23+
# Verify that constructs like `[ -t 1 ]` behave sensibly even inside a command substitution.
24+
25+
# This is the simple case that doesn't do any redirection of stdout within the command
26+
# substitution. Thus the [ -t 1 ] test should be false.
27+
send {v=$(echo begin; [ -t 1 ] && echo -t1 is true; echo end); echo "v=:$v:"}
28+
send "\r"
29+
expect "\r\nv=:begin\r\nend:\r\n" {
30+
puts "-t1 in comsub works correctly"
31+
}
32+
expect_prompt
33+
34+
# This is the more complex case that does redirect stdout within the command substitution to the
35+
# actual tty. Thus the [ -t 1 ] test should be true.
36+
send {v=$(echo begin; exec >/dev/tty; [ -t 1 ] && echo -t1 is true; echo end); echo "v=:$v:"}
37+
send "\r"
38+
expect "\r\n-t1 is true\r\nend\r\nv=:begin:\r\n" {
39+
puts "-t1 in comsub with exec >/dev/tty works correctly"
40+
}
41+
expect_prompt
42+
43+
# ==========
2244
# Exit shell with ctrl-d
2345
log_test_entry
2446
send [ctrl D]

src/cmd/ksh93/tests/test.exp.out

+2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
stdin is a terminal device
22
stdout is a terminal device
3+
-t1 in comsub works correctly
4+
-t1 in comsub with exec >/dev/tty works correctly

src/cmd/ksh93/tests/util/preamble.sh

+13
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,19 @@ function empty_fifos {
8585
}
8686
alias empty_fifos='empty_fifos $LINENO'
8787

88+
#
89+
# Verify that /dev/fd is functional. Note that on some systems (e.g., FreeBSD) there is a stub
90+
# /dev/fd that only supports 0, 1, and 2. On such systems a special pseudo-filesystem may need to
91+
# be mounted or a custom kernel built to get full /dev/fd support.
92+
#
93+
# Note that we can't do the straightforward `[[ -p /dev/fd/8 ]]` because such paths are
94+
# special-cased by ksh and work even if the system doesn't support /dev/fd. But there may be tests
95+
# where we need to know if those paths are recognized by the OS.
96+
#
97+
HAS_DEV_FD=no
98+
[[ $(print /dev/fd/*) == *' /dev/fd/8 '* ]] && HAS_DEV_FD=yes
99+
readonly HAS_DEV_FD
100+
88101
#
89102
# Platforms like OpenBSD have `jot` instead of `seq`. For the simple case of emitting ints from one
90103
# to n they are equivalent. And that should be the only use of `seq` in unit tests.

0 commit comments

Comments
 (0)