2013-09-15 15:56:29

by Oleg Nesterov

[permalink] [raw]
Subject: [PATCH 0/1] tty: disassociate_ctty() sends the extra SIGCONT

Hello.

I know nothing about tty's, I have no idea why disassociate_ctty()
checked on_exit before kill_pgrp(SIGCONT). And why this depends on
tty/old_pgrp/TTY_DRIVER_TYPE_PTY.

But this was changed in v3.10, probably by f91e2590 "tty: Signal
foreground group processes in hangup", and this breaks LSB tests
which do not expect the "wrong" SIGCONT.

The patch cc's stable, but it needs the ack or nack from someone
who these HUP/CONT rules.

IOW, I won't argue if the user-visible (and undocumented) change
added by f91e2590 is actually fine, in this case LSB should be
fixed.

Oleg.


2013-09-15 15:56:45

by Oleg Nesterov

[permalink] [raw]
Subject: [PATCH 1/1] tty: disassociate_ctty() sends the extra SIGCONT

Starting from v3.10 (probably f91e2590 "tty: Signal foreground
group processes in hangup") disassociate_ctty() sends SIGCONT
if tty && on_exit. This breaks LSB test-suite, in particular
test8 in _exit.c and test40 in sigcon5.c.

Put the "!on_exit" check back to restore the old behaviour.

Cc: [email protected] # v3.10+
Signed-off-by: Oleg Nesterov <[email protected]>
Reported-by: Karel Srot <[email protected]>
---
drivers/tty/tty_io.c | 3 ++-
1 files changed, 2 insertions(+), 1 deletions(-)

diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c
index a9355ce..3a1a01a 100644
--- a/drivers/tty/tty_io.c
+++ b/drivers/tty/tty_io.c
@@ -854,7 +854,8 @@ void disassociate_ctty(int on_exit)
struct pid *tty_pgrp = tty_get_pgrp(tty);
if (tty_pgrp) {
kill_pgrp(tty_pgrp, SIGHUP, on_exit);
- kill_pgrp(tty_pgrp, SIGCONT, on_exit);
+ if (!on_exit)
+ kill_pgrp(tty_pgrp, SIGCONT, on_exit);
put_pid(tty_pgrp);
}
}
--
1.5.5.1

2013-09-16 22:16:45

by Peter Hurley

[permalink] [raw]
Subject: Re: [PATCH 1/1] tty: disassociate_ctty() sends the extra SIGCONT

On 09/15/2013 11:50 AM, Oleg Nesterov wrote:
> Starting from v3.10 (probably f91e2590 "tty: Signal foreground
> group processes in hangup") disassociate_ctty() sends SIGCONT
> if tty && on_exit. This breaks LSB test-suite, in particular
> test8 in _exit.c and test40 in sigcon5.c.
>
> Put the "!on_exit" check back to restore the old behaviour.
>
> Cc: [email protected] # v3.10+
> Signed-off-by: Oleg Nesterov <[email protected]>
> Reported-by: Karel Srot <[email protected]>

Although I confirmed your results with a new unit test,
I'd like to review the source code for the reported tests.
Where can grab the source for the LSB tests, _exit.c and sigcon5.c?
Direct links would be appreciated.

Regards,
Peter Hurley

2013-09-17 19:41:49

by Carlos O'Donell

[permalink] [raw]
Subject: Re: [PATCH 1/1] tty: disassociate_ctty() sends the extra SIGCONT

On 09/16/2013 06:16 PM, Peter Hurley wrote:
> On 09/15/2013 11:50 AM, Oleg Nesterov wrote:
>> Starting from v3.10 (probably f91e2590 "tty: Signal foreground
>> group processes in hangup") disassociate_ctty() sends SIGCONT
>> if tty && on_exit. This breaks LSB test-suite, in particular
>> test8 in _exit.c and test40 in sigcon5.c.
>>
>> Put the "!on_exit" check back to restore the old behaviour.
>>
>> Cc: [email protected] # v3.10+
>> Signed-off-by: Oleg Nesterov <[email protected]>
>> Reported-by: Karel Srot <[email protected]>
>
> Although I confirmed your results with a new unit test,
> I'd like to review the source code for the reported tests.
> Where can grab the source for the LSB tests, _exit.c and sigcon5.c?
> Direct links would be appreciated.

wget ftp://ftp.linuxfoundation.org/pub/lsb/test_suites/released-4.1/source/runtime/lsb-test-core-4.1.15-1.src.rpm
rpm2cpio lsb-test-core-4.1.15-1.src.rpm | cpio -idmv
tar zxvf lsb-test-core-4.1.15.tar.gz
tar zxvf lts_vsx-pcts-4.1.15.tgz

Source is at:
./tset/POSIX.os/procprim/_exit/_exit.c
./tset/POSIX.os/procprim/sigconcept/sigcon5.c

In all failures the tests are checking for SIGHUP to be sent
to a foreground process. It would appear that the additional
signal confuses the test.

Exactly what semantics should be followed do not seem to be
clearly covered by any standards.

Therefore it is likely just as valid to say that the LSB tests
need to be more robust in the face of the additional signal.

I have little experience when it comes this particular area
of the kernel or expected behaviour from other OSs.

Cheers,
Carlos.

2013-09-17 20:30:55

by Peter Hurley

[permalink] [raw]
Subject: Re: [PATCH 1/1] tty: disassociate_ctty() sends the extra SIGCONT

On 09/17/2013 03:41 PM, Carlos O'Donell wrote:
> On 09/16/2013 06:16 PM, Peter Hurley wrote:
>> On 09/15/2013 11:50 AM, Oleg Nesterov wrote:
>>> Starting from v3.10 (probably f91e2590 "tty: Signal foreground
>>> group processes in hangup") disassociate_ctty() sends SIGCONT
>>> if tty && on_exit. This breaks LSB test-suite, in particular
>>> test8 in _exit.c and test40 in sigcon5.c.
>>>
>>> Put the "!on_exit" check back to restore the old behaviour.
>>>
>>> Cc: [email protected] # v3.10+
>>> Signed-off-by: Oleg Nesterov <[email protected]>
>>> Reported-by: Karel Srot <[email protected]>
>>
>> Although I confirmed your results with a new unit test,
>> I'd like to review the source code for the reported tests.
>> Where can grab the source for the LSB tests, _exit.c and sigcon5.c?
>> Direct links would be appreciated.
>
> wget ftp://ftp.linuxfoundation.org/pub/lsb/test_suites/released-4.1/source/runtime/lsb-test-core-4.1.15-1.src.rpm
> rpm2cpio lsb-test-core-4.1.15-1.src.rpm | cpio -idmv
> tar zxvf lsb-test-core-4.1.15.tar.gz
> tar zxvf lts_vsx-pcts-4.1.15.tgz
>
> Source is at:
> ./tset/POSIX.os/procprim/_exit/_exit.c
> ./tset/POSIX.os/procprim/sigconcept/sigcon5.c

Hi Carlos,

Thanks for the links.

> In all failures the tests are checking for SIGHUP to be sent
> to a foreground process. It would appear that the additional
> signal confuses the test.
>
> Exactly what semantics should be followed do not seem to be
> clearly covered by any standards.
>
> Therefore it is likely just as valid to say that the LSB tests
> need to be more robust in the face of the additional signal.
>
> I have little experience when it comes this particular area
> of the kernel or expected behaviour from other OSs.

The _exit.c:test8() and sigcon5.c:test40() look correct to me.
The fact that they are fragile IMO is a good thing; I could
easily envision an app's signal handling being equally fragile.

Although SUSv3 & v4 don't preclude the extra SIGCONT, in this specific case,
a pty should receive the same signals, in the same order as a regular tty.

Thanks again,
Peter Hurley

2013-09-17 20:39:13

by Peter Hurley

[permalink] [raw]
Subject: Re: [PATCH 1/1] tty: disassociate_ctty() sends the extra SIGCONT

On 09/15/2013 11:50 AM, Oleg Nesterov wrote:
> Starting from v3.10 (probably f91e2590 "tty: Signal foreground
> group processes in hangup") disassociate_ctty() sends SIGCONT
> if tty && on_exit. This breaks LSB test-suite, in particular
> test8 in _exit.c and test40 in sigcon5.c.

Yes, this regression was introduced by me in that commit.
The effect of the regression is that ptys will receive a
SIGCONT when, in similar circumstances, ttys would not.

The fact that two test vectors accidentally tripped over
this regression suggests that some other apps may as well.

Thanks for catching this.

> Put the "!on_exit" check back to restore the old behaviour.
>
> Cc: [email protected] # v3.10+
> Signed-off-by: Oleg Nesterov <[email protected]>
> Reported-by: Karel Srot <[email protected]>

Reviewed-by: Peter Hurley <[email protected]>

> ---
> drivers/tty/tty_io.c | 3 ++-
> 1 files changed, 2 insertions(+), 1 deletions(-)
>
> diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c
> index a9355ce..3a1a01a 100644
> --- a/drivers/tty/tty_io.c
> +++ b/drivers/tty/tty_io.c
> @@ -854,7 +854,8 @@ void disassociate_ctty(int on_exit)
> struct pid *tty_pgrp = tty_get_pgrp(tty);
> if (tty_pgrp) {
> kill_pgrp(tty_pgrp, SIGHUP, on_exit);
> - kill_pgrp(tty_pgrp, SIGCONT, on_exit);
> + if (!on_exit)
> + kill_pgrp(tty_pgrp, SIGCONT, on_exit);
> put_pid(tty_pgrp);
> }
> }
>

2013-09-21 18:41:05

by Oleg Nesterov

[permalink] [raw]
Subject: Re: [PATCH 1/1] tty: disassociate_ctty() sends the extra SIGCONT

Peter, sorry for delay, I was sick.

On 09/17, Peter Hurley wrote:
>
> On 09/15/2013 11:50 AM, Oleg Nesterov wrote:
>
>> Put the "!on_exit" check back to restore the old behaviour.
>>
>> Cc: [email protected] # v3.10+
>> Signed-off-by: Oleg Nesterov <[email protected]>
>> Reported-by: Karel Srot <[email protected]>
>
> Reviewed-by: Peter Hurley <[email protected]>

Thanks!

Can I ask the question? tty_signal_session_leader() is probably fine,
but it _looks_ buggy or at least confusing to me.

do_each_pid_task(tty->session, PIDTYPE_SID, p) {
spin_lock_irq(&p->sighand->siglock);
if (p->signal->tty == tty) {
p->signal->tty = NULL;
/* We defer the dereferences outside fo
the tasklist lock */
refs++;
}
if (!p->signal->leader) {
spin_unlock_irq(&p->sighand->siglock);
continue;
}
__group_send_sig_info(SIGHUP, SEND_SIG_PRIV, p);
__group_send_sig_info(SIGCONT, SEND_SIG_PRIV, p);
put_pid(p->signal->tty_old_pgrp); /* A noop */
spin_lock(&tty->ctrl_lock);
tty_pgrp = get_pid(tty->pgrp);

I guess this can happen only once, so we could even add WARN_ON(tty_pgrp)
before get_pid(). But this look confusing, as if we can do get_pid()
multiple times and leak tty->pgrp.

if (tty->pgrp)
p->signal->tty_old_pgrp = get_pid(tty->pgrp);

else? We already did put_pid(tty_old_pgrp), we should clear it.

IOW, do you think the patch below makes sense or I missed something?
Just curious.

Oleg.

--- x/drivers/tty/tty_io.c
+++ x/drivers/tty/tty_io.c
@@ -548,7 +548,7 @@ static int tty_signal_session_leader(str
{
struct task_struct *p;
int refs = 0;
- struct pid *tty_pgrp = NULL;
+ struct pid *tty_pgrp = tty_get_pgrp(tty);

read_lock(&tasklist_lock);
if (tty->session) {
@@ -560,18 +560,12 @@ static int tty_signal_session_leader(str
the tasklist lock */
refs++;
}
- if (!p->signal->leader) {
- spin_unlock_irq(&p->sighand->siglock);
- continue;
+ if (p->signal->leader) {
+ __group_send_sig_info(SIGHUP, SEND_SIG_PRIV, p);
+ __group_send_sig_info(SIGCONT, SEND_SIG_PRIV, p);
+ put_pid(p->signal->tty_old_pgrp); /* A noop */
+ p->signal->tty_old_pgrp = get_pid(tty_pgrp);
}
- __group_send_sig_info(SIGHUP, SEND_SIG_PRIV, p);
- __group_send_sig_info(SIGCONT, SEND_SIG_PRIV, p);
- put_pid(p->signal->tty_old_pgrp); /* A noop */
- spin_lock(&tty->ctrl_lock);
- tty_pgrp = get_pid(tty->pgrp);
- if (tty->pgrp)
- p->signal->tty_old_pgrp = get_pid(tty->pgrp);
- spin_unlock(&tty->ctrl_lock);
spin_unlock_irq(&p->sighand->siglock);
} while_each_pid_task(tty->session, PIDTYPE_SID, p);
}

2013-09-21 20:25:16

by Peter Hurley

[permalink] [raw]
Subject: Re: [PATCH 1/1] tty: disassociate_ctty() sends the extra SIGCONT

On 09/21/2013 02:34 PM, Oleg Nesterov wrote:
> Peter, sorry for delay, I was sick.
>
> On 09/17, Peter Hurley wrote:
>>
>> On 09/15/2013 11:50 AM, Oleg Nesterov wrote:
>>
>>> Put the "!on_exit" check back to restore the old behaviour.
>>>
>>> Cc: [email protected] # v3.10+
>>> Signed-off-by: Oleg Nesterov <[email protected]>
>>> Reported-by: Karel Srot <[email protected]>
>>
>> Reviewed-by: Peter Hurley <[email protected]>
>
> Thanks!
>
> Can I ask the question? tty_signal_session_leader() is probably fine,
> but it _looks_ buggy or at least confusing to me.
>
> do_each_pid_task(tty->session, PIDTYPE_SID, p) {
> spin_lock_irq(&p->sighand->siglock);
> if (p->signal->tty == tty) {
> p->signal->tty = NULL;
> /* We defer the dereferences outside fo
> the tasklist lock */
> refs++;
> }
> if (!p->signal->leader) {
> spin_unlock_irq(&p->sighand->siglock);
> continue;
> }
> __group_send_sig_info(SIGHUP, SEND_SIG_PRIV, p);
> __group_send_sig_info(SIGCONT, SEND_SIG_PRIV, p);
> put_pid(p->signal->tty_old_pgrp); /* A noop */
> spin_lock(&tty->ctrl_lock);
> tty_pgrp = get_pid(tty->pgrp);
>
> I guess this can happen only once, so we could even add WARN_ON(tty_pgrp)
> before get_pid(). But this look confusing, as if we can do get_pid()
> multiple times and leak tty->pgrp.
>
> if (tty->pgrp)
> p->signal->tty_old_pgrp = get_pid(tty->pgrp);
>
> else? We already did put_pid(tty_old_pgrp), we should clear it.
>
> IOW, do you think the patch below makes sense or I missed something?
> Just curious.

The code block you're referring to only executes once because there is
only one session leader.

Regards,
Peter Hurley

2013-09-22 20:10:21

by Oleg Nesterov

[permalink] [raw]
Subject: Re: [PATCH 1/1] tty: disassociate_ctty() sends the extra SIGCONT

On 09/21, Peter Hurley wrote:
>
> On 09/21/2013 02:34 PM, Oleg Nesterov wrote:
>>
>> do_each_pid_task(tty->session, PIDTYPE_SID, p) {
>> spin_lock_irq(&p->sighand->siglock);
>> if (p->signal->tty == tty) {
>> p->signal->tty = NULL;
>> /* We defer the dereferences outside fo
>> the tasklist lock */
>> refs++;
>> }
>> if (!p->signal->leader) {
>> spin_unlock_irq(&p->sighand->siglock);
>> continue;
>> }
>> __group_send_sig_info(SIGHUP, SEND_SIG_PRIV, p);
>> __group_send_sig_info(SIGCONT, SEND_SIG_PRIV, p);
>> put_pid(p->signal->tty_old_pgrp); /* A noop */
>> spin_lock(&tty->ctrl_lock);
>> tty_pgrp = get_pid(tty->pgrp);
>>
>> I guess this can happen only once, so we could even add WARN_ON(tty_pgrp)
>> before get_pid(). But this look confusing, as if we can do get_pid()
>> multiple times and leak tty->pgrp.
>>
>> if (tty->pgrp)
>> p->signal->tty_old_pgrp = get_pid(tty->pgrp);
>>
>> else? We already did put_pid(tty_old_pgrp), we should clear it.
>>
>> IOW, do you think the patch below makes sense or I missed something?
>> Just curious.
>
> The code block you're referring to only executes once because there is
> only one session leader.

I understand, and I even mentioned this above.

My point was, this _looks_ confusing, and the patch I sent makes it
more clean.

And what about ->tty_old_pgrp? I still think that at least the patch
below makes sense. If tty->pgrp == NULL is not possible here (I do
not know), then why do we check? Otherwise ->tty_old_pgrp != NULL looks
certainly wrong after put_pid().

Oleg.

--- x/drivers/tty/tty_io.c
+++ x/drivers/tty/tty_io.c
@@ -569,8 +569,7 @@ static int tty_signal_session_leader(str
put_pid(p->signal->tty_old_pgrp); /* A noop */
spin_lock(&tty->ctrl_lock);
tty_pgrp = get_pid(tty->pgrp);
- if (tty->pgrp)
- p->signal->tty_old_pgrp = get_pid(tty->pgrp);
+ p->signal->tty_old_pgrp = get_pid(tty->pgrp);
spin_unlock(&tty->ctrl_lock);
spin_unlock_irq(&p->sighand->siglock);
} while_each_pid_task(tty->session, PIDTYPE_SID, p);

2013-09-24 18:18:23

by Peter Hurley

[permalink] [raw]
Subject: Re: [PATCH 1/1] tty: disassociate_ctty() sends the extra SIGCONT

On 09/22/2013 04:03 PM, Oleg Nesterov wrote:
> On 09/21, Peter Hurley wrote:
>>
>> On 09/21/2013 02:34 PM, Oleg Nesterov wrote:
>>>
>>> do_each_pid_task(tty->session, PIDTYPE_SID, p) {
>>> spin_lock_irq(&p->sighand->siglock);
>>> if (p->signal->tty == tty) {
>>> p->signal->tty = NULL;
>>> /* We defer the dereferences outside fo
>>> the tasklist lock */
>>> refs++;
>>> }
>>> if (!p->signal->leader) {
>>> spin_unlock_irq(&p->sighand->siglock);
>>> continue;
>>> }
>>> __group_send_sig_info(SIGHUP, SEND_SIG_PRIV, p);
>>> __group_send_sig_info(SIGCONT, SEND_SIG_PRIV, p);
>>> put_pid(p->signal->tty_old_pgrp); /* A noop */
>>> spin_lock(&tty->ctrl_lock);
>>> tty_pgrp = get_pid(tty->pgrp);
>>>
>>> I guess this can happen only once, so we could even add WARN_ON(tty_pgrp)
>>> before get_pid(). But this look confusing, as if we can do get_pid()
>>> multiple times and leak tty->pgrp.
>>>
>>> if (tty->pgrp)
>>> p->signal->tty_old_pgrp = get_pid(tty->pgrp);
>>>
>>> else? We already did put_pid(tty_old_pgrp), we should clear it.
>>>
>>> IOW, do you think the patch below makes sense or I missed something?
>>> Just curious.
>>
>> The code block you're referring to only executes once because there is
>> only one session leader.
>
> I understand, and I even mentioned this above.

Ah, ok. I didn't realize the earlier patch was a cleanup attempt and
not fixing something.

> My point was, this _looks_ confusing, and the patch I sent makes it
> more clean.

I agree that this looks confusing, but I'm not sure I agree that your earlier
patch makes it cleaner; maybe a code comment stating that the block only
executes once for the session leader would be more appropriate.

Also, I put the get_pid() in the siglock critical section to prevent
unsafe racing between hangup and ioctl(TIOCSCTTY).

> And what about ->tty_old_pgrp? I still think that at least the patch
> below makes sense. If tty->pgrp == NULL is not possible here (I do
> not know), then why do we check?

tty->pgrp can be NULL here if the session leader is dropping the
controlling terminal association via no_tty(). But in this
case ->tty_old_pgrp will also be NULL.

This race should probably be eliminated by claiming the tty_lock()
in no_tty(), so that it doesn't race with __tty_hangup() at all.

[NB: The other possibility, a second hangup, is no longer possible.]

> Otherwise ->tty_old_pgrp != NULL looks certainly wrong after put_pid().

I agree this looks fairly suspect; so does

put_pid(p->signal->tty_old_pgrp); /* A noop */

not because of the comment, but because tty_old_pgrp cannot be non-NULL
here:
1. The session leader's tty_old_pgrp is only assigned non-NULL if its
controlling terminal is hung up.
2. The tty cannot be hung up more than once.
3. If the session leader changes the controlling tty via ioctl(TIOCSCTTY),
__proc_set_tty() will put_pid(tty_old_pgrp) and reset it to NULL.
[so tty_old_pgrp is NULL on a subsequent hangup of the new controlling tty].
4. If the session leader drops the controlling terminal association
via ioctl(TIOCNOTTY), disassociate_tty() will put_pid(tty_old_pgrp)
and reset it to NULL. [Assuming the race mentioned above is fixed,
then there is no controlling tty to hangup.]

What about replacing
put_pid(p->signal->tty_old_pgrp); /* A noop */
with
WARN_ON(p->signal->tty_old_pgrp);
?

And fixing the FIXME in no_tty()?

Regards,
Peter Hurley

> --- x/drivers/tty/tty_io.c
> +++ x/drivers/tty/tty_io.c
> @@ -569,8 +569,7 @@ static int tty_signal_session_leader(str
> put_pid(p->signal->tty_old_pgrp); /* A noop */
> spin_lock(&tty->ctrl_lock);
> tty_pgrp = get_pid(tty->pgrp);
> - if (tty->pgrp)
> - p->signal->tty_old_pgrp = get_pid(tty->pgrp);
> + p->signal->tty_old_pgrp = get_pid(tty->pgrp);
> spin_unlock(&tty->ctrl_lock);
> spin_unlock_irq(&p->sighand->siglock);
> } while_each_pid_task(tty->session, PIDTYPE_SID, p);

2013-09-25 16:27:59

by Oleg Nesterov

[permalink] [raw]
Subject: v3.10 breaks T.tcflush (Was: tty: disassociate_ctty() sends the extra SIGCONT)

Peter, thanks for your explanations about tty_old_pgrp/etc,
I'll try to reply later. I need to read your email carefully.

FYI, LSB tests report another failure starting from 3.10.

See lsb-test-core/lts_vsx-pcts/tset/POSIX.os/devclass/tcflush/tcflush.c
attached below. test4() fails with

SIGTTOU not received
tcflush(TCIOFLUSH) action was performed
when TOSTOP was set

I'll try to investigate, but let me repeat I know absolutely nothing
about drivers/tty/, termios, etc. Perhaps you can take a look? So far I
didn't even look into the attached code, I have no idea what it does.

Thanks,

Oleg.

-------------------------------------------------------------------------------
/*
* SCCS: @(#) POSIX.os/devclass/tcflush/tcflush.c Rel 4.4.1 (06/09/97)
*
* UniSoft Ltd., London, England
*
* (C) Copyright 1991 X/Open Company Limited
*
* All rights reserved. No part of this source code may be reproduced,
* stored in a retrieval system, or transmitted, in any form or by any
* means, electronic, mechanical, photocopying, recording or otherwise,
* except as stated in the end-user licence agreement, without the prior
* permission of the copyright owners.
*
* X/Open and the 'X' symbol are trademarks of X/Open Company Limited in
* the UK and other countries.
*/

#ifndef lint
#define MAIN
#ifdef MAIN
static char sccsid[] = "@(#)POSIX.os/devclass/tcflush - T.tcflush Rel 4.4.1 (06/09/97)";
char *copyright[] = {
"(C) Copyright 1991 X/Open Company Limited",
"All Rights Reserved."
};
#endif /* MAIN */
#endif /* lint */

/***********************************************************************

NAME: T.tcflush

PROJECT: VSX (X/OPEN Validation Suite)

DESCRIPTION:
Testset for the tcflush() system call.

METHODS:
ASSUMES:
CONDITIONAL COMPILE PARAMS:
SIDE EFFECTS:
RELATED DOCUMENTS:
SOURCE:

AUTHOR: Geoff Clare, UniSoft Ltd.
DATE CREATED: 20 Jan 1989
MODIFICATIONS: J.A.Nave 29/06/90
Check for XXX_prep() functions returning 1 to
indicate GTI unsupported

David Sawyer
Tue Aug 07 12:05:21 BST 1990
Added resilience code.

Stuart Boutell, 20 May 1992
SIGTTOUT test now performed with TOSTOP mode set
and clear.

Stuart Boutell, 21 May 1992
Removed dependency on GTI for EBADF and ENOTTY tests

Stuart Boutell, 3 June 1992
Add non-controlling terminal tests.

Geoff Clare, 9 June 1992
Allow for systems with no terminal output queue.

Geoff Clare, 9 June 1993
Change tcflow() call to tcflush() in test 14.

Geoff Clare, 10 Feb 1995
Use do_fnoeio() instead of do_feio().

************************************************************************/

#include <std.h>
#include <stdio.h>
#include <sys/types.h>
#include <dbug.h>
#include <report.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <termios.h>
#include <timeout.h>
#include <tet_api.h>
#include <sysdep.h>

#ifdef UNDEF_MACROS
#undef tcflush
#endif


#define NO_TESTS 14
#define WAITTIME (5 * SPEEDFACTOR)
#define NAP 2
#define READ_TIME 20
#define MODEANY (S_IRWXU|S_IRWXG|S_IRWXO)
#define NOK 002
#define E_DEL 004
#define E_SAFAIL 010
#define E_GOTSIG 020
#define PATH_OK 040

#define USE_TTY 0x1
#define USE_LOOP 0x0

#define BUFLEN (sizeof(outbuf)-1)

extern char *errname();
extern void do_fnoeio();
extern void xx_all();

int no_tests = NO_TESTS;

private void test1();
private void test2();
private void test3();
private void test4();
private void do_test4();
private void test5();
private void test6();
private void test7();
private void test8();
private void test9();
private void test10();
private void test11();
private void test12();
private void do_test12();
private void test13();
private void test14();
private void ch_t4();
private void ch_t5();
private void ch_t6();
private void ch_t10();
private void ch_t12();
private void ch_t13();
private void gch_t5();
private void gch_t6();
private void gch_t10();
private void gch_t13();
private void t14rpt();
private int t14io();
private void prepare_tests();
private void clean_tests();
private void twrap();
private int set_tostop();
private int clear_tostop();

public void (*realfuncs[]) () = { test1, test2, test3, test4, test5,
test6, NULL, test8, NULL,
test10, test11, test12, test13, test14,
NULL };
public void (*setfunc) () = prepare_tests;
public void (*clnfunc) () = clean_tests;
public struct tet_testlist tet_testlist[] = {
twrap, 1,
twrap, 2,
twrap, 3,
twrap, 4,
twrap, 5,
twrap, 6,
test7, 7,
twrap, 8,
test9, 9,
twrap, 10,
twrap, 11,
twrap, 12,
twrap, 13,
twrap, 14,
NULL, 0
};

private int unsupported = 0;
private char *tfile = "./tcflush-t";
private char outbuf[] = "'Twas brillig, and the slithy toves\nDid gyre ...";
private char inbuf[512];
private int tty_fildes, loop_fildes;
private struct termios tty_termios, loop_termios,
*loop_termios_p = &loop_termios,
*tty_termios_p = &tty_termios;
private int input, output; /* read() returns */
private int caught_sig = 0;
private int tc_ret;
private int testfail;
private int buffered;

private void
sig_catch(sig)
{
caught_sig = sig;
}


private void
do_looptcflush()
{
int pathok = 0;

DBUG_ENTER("do_tcflush");

tc_ret = tcflush(loop_fildes, TCIOFLUSH);

PATH_FUNC_RPT(0); /* cppair expects globok to be set */

DBUG_VOID_RETURN;
}
private void
do_tcflush()
{
int pathok = 0;

DBUG_ENTER("do_tcflush");

tc_ret = tcflush(tty_fildes, TCIOFLUSH);

PATH_FUNC_RPT(0); /* cppair expects globok to be set */

DBUG_VOID_RETURN;
}

private void
twrap()
{
/* wrapper to check if unsupported and call real test function */

DBUG_ENTER("twrap");

if (unsupported)
{
xx_rpt(UNSUPPORTED);
in_rpt("VSX_TERMIOS_TTY/LOOP is set to \"unsup\"");
DBUG_VOID_RETURN;
}

if (tet_thistest >= 1 && tet_thistest <= no_tests)
(*realfuncs[tet_thistest-1])();

DBUG_VOID_RETURN;
}

private void
prepare_tests()
{
char *msg = NULL;
char *cp;
int i;

DBUG_ENTER("prepare_tests");

cp = tet_getvar("VSX_TERMIOS_BUFFERED");
if (cp == NULL || *cp == '\0')
{
xx_all(DELETION, "parameter VSX_TERMIOS_BUFFERED is not set");
DBUG_VOID_RETURN;
}
else if (*cp == 'N' || *cp == 'n')
buffered = 0;
else
buffered = 1;

/* Open and initialize terminal file */

switch (termios_prep (&tty_fildes, tty_termios_p,
&loop_fildes, loop_termios_p))
{
case 0:
break;
case 1:
unsupported = 1;
DBUG_VOID_RETURN;
/*NOTREACHED*/
break;
default:
if (alrm_flag > 0)
msg = "open/initialise VSX_TERMIOS_TTY and VSX_TERMIOS_LOOP timed out";
else
msg = "could not open/initialise VSX_TERMIOS_TTY and VSX_TERMIOS_LOOP";
for (i = 1; i <= 6; i++)
(void) tet_delete(i, msg);
(void) tet_delete(8, msg);
DBUG_VOID_RETURN;
}

loop_termios_p->c_lflag &= ~ICANON;
loop_termios_p->c_cc[VTIME] = READ_TIME;
loop_termios_p->c_cc[VMIN] = 0;

if (tcsetattr(loop_fildes, TCSANOW, loop_termios_p) == SYSERROR)
{
for (i = 1; i <= NO_TESTS; i++)
(void) tet_delete(i, "tcsetattr(TCSANOW) failed on VSX_TERMIOS_LOOP in preparation");
DBUG_VOID_RETURN;
}

tty_termios_p->c_lflag |= TOSTOP;
tty_termios_p->c_iflag |= IXON;
tty_termios_p->c_lflag &= ~ICANON;
tty_termios_p->c_cc[VTIME] = READ_TIME;
tty_termios_p->c_cc[VMIN] = 0;

if (tcsetattr(tty_fildes, TCSANOW, tty_termios_p) == SYSERROR)
{
for (i = 1; i <= NO_TESTS; i++)
(void) tet_delete(i, "tcsetattr(TCSANOW) failed on VSX_TERMIOS_TTY in preparation");
DBUG_VOID_RETURN;
}

SET_TIMEOUT(WAITTIME)

/* check if writes/reads are working */

if (write(loop_fildes, outbuf, BUFLEN) != BUFLEN)
msg = "write() to VSX_TERMIOS_LOOP failed in preparation";
else if (write(tty_fildes, outbuf, BUFLEN) != BUFLEN)
msg = "write() to VSX_TERMIOS_TTY failed in preparation";

CLEAR_ALARM

if (msg == NULL)
{
(void) sleep(NAP);

SET_TIMEOUT(WAITTIME)

if (read(loop_fildes, inbuf, sizeof(inbuf)) != BUFLEN)
msg = "read() from VSX_TERMIOS_LOOP failed in preparation";
else if (read(tty_fildes, inbuf, sizeof(inbuf)) != BUFLEN)
msg = "read() from VSX_TERMIOS_TTY failed in preparation";

CLEAR_ALARM
}

if (msg != NULL)
{
for (i = 1; i <= NO_TESTS; i++)
(void) tet_delete(i, msg);
}

DBUG_VOID_RETURN;
}

private void
clean_tests()
{
DBUG_ENTER("clean_tests");

if (!unsupported) {
(void) close(tty_fildes);
(void) close(loop_fildes);
}
(void) unlink(tfile);

DBUG_VOID_RETURN;
}

private int
tprep(canon, use_tty)
int canon; /* true if ICANON should be turned on */
int use_tty; /* true if tty_fildes is to be tested.
false if loop_fildes is to be tested. */
{
int fail = 0;
int err;
int pathok = 0;

DBUG_ENTER("tprep");

if (canon) {
if (use_tty)
tty_termios_p->c_lflag |= ICANON;
else
loop_termios_p->c_lflag |= ICANON;
} else{
if (use_tty)
tty_termios_p->c_lflag &= ~ICANON;
else
loop_termios_p->c_lflag &= ~ICANON;
}

if (tcsetattr( (use_tty?tty_fildes:loop_fildes), TCSANOW,
( use_tty?tty_termios_p:loop_termios_p )) == SYSERROR)
{
err = errno;
if (testfail++ == 0)
xx_rpt(DELETION);
in_rpt("tcsetattr(TCSANOW) failed on VSX_TERMIOS_%s - errno %d (%s)",
(use_tty?"TTY":"LOOP"), err, errname(err));
DBUG_RETURN(-1);
}
else
PATH_TRACE;

SET_TIMEOUT(WAITTIME)

if (buffered && tcflow( (use_tty?tty_fildes:loop_fildes), TCOOFF) == SYSERROR)
{
fail = 1;
err = errno;
}
else if (write(loop_fildes, outbuf, BUFLEN) != BUFLEN)
fail = 2;
else if (write(tty_fildes, outbuf, BUFLEN) != BUFLEN)
fail = 3;
else
PATH_TRACE;

CLEAR_ALARM

if (fail != 0)
{
if (testfail++ == 0)
xx_rpt(DELETION);
switch (fail)
{
case 1:
in_rpt("tcflow(TCOOFF) failed on VSX_TERMIOS_%s - errno %d (%s)",
(use_tty?"TTY":"LOOP"), err, errname(err));
break;
case 2:
in_rpt("write() to VSX_TERMIOS_LOOP failed");
break;
case 3:
in_rpt("write() to VSX_TERMIOS_TTY failed");
break;
}
DBUG_RETURN(-1);
}
else
PATH_TRACE;

(void) sleep(NAP);

PATH_FUNC_RPT(3);

DBUG_RETURN(0);
}

private int
clear_tostop(use_tty)
int use_tty;
{
int pathok = 0;

DBUG_ENTER("clear_tostop");

if (use_tty)
tty_termios_p->c_lflag &= ~(TOSTOP);
else
loop_termios_p->c_lflag &= ~(TOSTOP);

if (tcsetattr( (use_tty? tty_fildes : loop_fildes), TCSANOW,
(use_tty? tty_termios_p : loop_termios_p) ) == SYSERROR)
{
DBUG_RETURN(0);
} else
PATH_TRACE;

PATH_FUNC_RPT(1);

DBUG_RETURN(1);
}

private int
set_tostop(use_tty)
int use_tty;
{
int pathok = 0;

DBUG_ENTER("set_tostop");

if (use_tty)
tty_termios_p->c_lflag |= TOSTOP;
else
loop_termios_p->c_lflag |= TOSTOP;

if (tcsetattr( (use_tty? tty_fildes : loop_fildes), TCSANOW,
(use_tty? tty_termios_p : loop_termios_p) ) == SYSERROR)
{
DBUG_RETURN(0);
} else
PATH_TRACE;

PATH_FUNC_RPT(1);

DBUG_RETURN(1);
}


private int
tread(use_tty)
int use_tty;
{
int ret, err;
int pathok = 0;

DBUG_ENTER("tread");

SET_TIMEOUT(WAITTIME)

ret = tcflow( (use_tty ? tty_fildes : loop_fildes), TCOON);
err = errno;

/* TCION call deleted (see comments on TCIOFF in tprep()) */

CLEAR_ALARM

if (ret == SYSERROR)
{
if (testfail++ == 0)
xx_rpt(DELETION);
in_rpt("tcflow(TCOON) failed on VSX_TERMIOS_%s - errno %d (%s)",
(use_tty ? "TTY" : "LOOP"), err, errname(err));
DBUG_RETURN(-1);
}
else
PATH_TRACE;

(void) sleep(NAP);

input = 0;

SET_TIMEOUT(WAITTIME)

output = read( (use_tty ? loop_fildes : tty_fildes), inbuf, sizeof(inbuf));
if (output >= 0)
{
PATH_TRACE;
while (read( (use_tty ? tty_fildes : loop_fildes), inbuf, 1) > 0)
++input;
}
else
PATH_TRACE;

CLEAR_ALARM

if (output < 0)
{
if (testfail++ == 0)
xx_rpt(DELETION);
in_rpt("read() from VSX_TERMIOS_%s failed",
(use_tty ? "LOOP":"TTY"));
DBUG_RETURN(-1);
}
else
PATH_TRACE;

PATH_FUNC_RPT(3);

DBUG_RETURN(0);
}


private void
test1()
{
int rval, err;
int pathok = 0;

DBUG_ENTER("test1");

testfail = 0;

globok = 0;
if (tprep(0, USE_TTY) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

globok = 0;
if ( ! set_tostop(USE_TTY) ) {
xx_rpt(DELETION);
in_rpt("tcsetattr(TCSANOW) to set TOSTOP failed on VSX_TERMIOS_TTY");
DBUG_VOID_RETURN;
} else
PATH_FUNC_TRACE;


SET_TIMEOUT(WAITTIME)

rval = tcflush(tty_fildes, TCIFLUSH);
err = errno;

CLEAR_ALARM

if (rval != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIFLUSH) returned %d, expected 0", rval);
in_rpt("errno was set to %d (%s)", err, errname(err));
}
else
PATH_TRACE;

globok = 0;
if (tread(USE_TTY) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

if (output < BUFLEN)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIFLUSH) discarded output (non-canonical mode)");
}
else
PATH_TRACE;

if (input != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIFLUSH) did not discard input (non-canonical mode)");
}
else
PATH_TRACE;

/* repeat for canonical mode */

globok = 0;
if (tprep(1, USE_TTY) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

SET_TIMEOUT(WAITTIME)

rval = tcflush(tty_fildes, TCIFLUSH);
err = errno;

CLEAR_ALARM

if (rval != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIFLUSH) returned %d, expected 0", rval);
in_rpt("errno was set to %d (%s)", err, errname(err));
}
else
PATH_TRACE;

globok = 0;
if (tread(USE_TTY) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

if (output < BUFLEN)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIFLUSH) discarded output (canonical mode)");
}
else
PATH_TRACE;

if (input != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIFLUSH) did not discard input (canonical mode)");
}
else
PATH_TRACE;

if (testfail == 0)
PATH_XS_RPT(11);

DBUG_VOID_RETURN;
}

private void
test2()
{
int rval, err;
int pathok = 0;

DBUG_ENTER("test2");

testfail = 0;

globok = 0;
if (tprep(0, USE_TTY) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

globok = 0;
if ( ! set_tostop(USE_TTY) ) {
xx_rpt(DELETION);
in_rpt("tcsetattr(TCSANOW) to set TOSTOP failed on VSX_TERMIOS_TTY");
DBUG_VOID_RETURN;
} else
PATH_FUNC_TRACE;

SET_TIMEOUT(WAITTIME)

rval = tcflush(tty_fildes, TCOFLUSH);
err = errno;

CLEAR_ALARM

if (rval != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCOFLUSH) returned %d, expected 0", rval);
in_rpt("errno was set to %d (%s)", err, errname(err));
}
else
PATH_TRACE;

globok = 0;
if (tread(USE_TTY) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

if (input < BUFLEN)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCOFLUSH) discarded input");
}
else
PATH_TRACE;

if (buffered && output != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCOFLUSH) did not discard output");
}
else
PATH_TRACE;

if (testfail == 0)
PATH_XS_RPT(6);

DBUG_VOID_RETURN;
}

private void
test3()
{
int rval, err;
int pathok = 0;

DBUG_ENTER("test3");

testfail = 0;

globok = 0;
if (tprep(0, USE_TTY) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

globok = 0;
if ( ! set_tostop(USE_TTY) ) {
xx_rpt(DELETION);
in_rpt("tcsetattr(TCSANOW) to set TOSTOP failed on VSX_TERMIOS_TTY");
DBUG_VOID_RETURN;
} else
PATH_FUNC_TRACE;

SET_TIMEOUT(WAITTIME)

rval = tcflush(tty_fildes, TCIOFLUSH);
err = errno;

CLEAR_ALARM

if (rval != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIOFLUSH) returned %d, expected 0", rval);
in_rpt("errno was set to %d (%s)", err, errname(err));
}
else
PATH_TRACE;

globok = 0;
if (tread(USE_TTY) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

if (input != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIOFLUSH) did not discard input (non-canonical mode)");
}
else
PATH_TRACE;

if (buffered && output != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIOFLUSH) did not discard output (non-canonical mode)");
}
else
PATH_TRACE;

/* repeat for canonical mode */

globok = 0;
if (tprep(1, USE_TTY) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

SET_TIMEOUT(WAITTIME)

rval = tcflush(tty_fildes, TCIOFLUSH);
err = errno;

CLEAR_ALARM

if (rval != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIOFLUSH) returned %d, expected 0", rval);
in_rpt("errno was set to %d (%s)", err, errname(err));
}
else
PATH_TRACE;

globok = 0;
if (tread(USE_TTY) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

if (input != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIOFLUSH) did not discard input (canonical mode)");
}
else
PATH_TRACE;

if (buffered && output != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIOFLUSH) did not discard output (canonical mode)");
}
else
PATH_TRACE;

if (testfail == 0)
PATH_XS_RPT(11);

DBUG_VOID_RETURN;
}

private void
test4()
{
int pathok = 0;

DBUG_ENTER("test4");

if (sysconf(_SC_JOB_CONTROL) == -1)
{
xx_rpt(UNSUPPORTED);
in_rpt("_POSIX_JOB_CONTROL not defined");
DBUG_VOID_RETURN;
}
else
PATH_TRACE;

globok = 0;
if ( ! set_tostop(USE_TTY) ) {
xx_rpt(DELETION);
in_rpt("tcsetattr(TCSANOW) to set TOSTOP failed on VSX_TERMIOS_TTY");
DBUG_VOID_RETURN;
} else
PATH_FUNC_TRACE;

testfail = 0;

globok = 0;
do_test4();
PATH_FUNC_TRACE;

if (testfail != 0) {
in_rpt("when TOSTOP was set");
} else {

globok = 0;
if ( ! clear_tostop(USE_TTY) ) {
xx_rpt(DELETION);
in_rpt("tcsetattr(TCSANOW) to clear TOSTOP failed on VSX_TERMIOS_TTY");
DBUG_VOID_RETURN;
} else
PATH_FUNC_TRACE;

globok = 0;
do_test4();
PATH_FUNC_TRACE;

if (testfail != 0) {
in_rpt("when TOSTOP was clear");
} else
PATH_TRACE;
}


if (testfail == 0)
PATH_XS_RPT(6);


DBUG_VOID_RETURN;
}

private
void
do_test4()
{
int pathok = 0;

DBUG_ENTER("do_test4");

globok = 0;
if (tprep(0, USE_TTY) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

switch (cppair(ch_t4, NULLFN, WAITTIME, E_DEL|NOK|PATH_OK))
{
case E_DEL:
DBUG_VOID_RETURN;
case NOK:
testfail++; /* reported in child */
break;
case PATH_OK:
PATH_TRACE;
}

globok = 0;
if (tread(USE_TTY) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

if (input < BUFLEN || output < BUFLEN)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIOFLUSH) action was performed");
}
else
PATH_TRACE;

globok=0;
if (testfail == 0)
PATH_FUNC_RPT(4);

DBUG_VOID_RETURN;
}

private void
ch_t4()
{
struct sigaction sig;
int pathok = 0;

DBUG_ENTER("ch_t4");

if (setpgid((pid_t)0, (pid_t)0) == SYSERROR)
{
xx_rpt(DELETION);
in_rpt("setpgid(0, 0) failed");
DBUG_EXIT(E_DEL);
}
else
PATH_TRACE;

sig.sa_handler = sig_catch;
sig.sa_flags = 0;
(void) sigemptyset(&sig.sa_mask);
if (sigaction(SIGTTOU, &sig, NULLSA) == SYSERROR)
{
xx_rpt(DELETION);
in_rpt("sigaction(SIGTTOU, ...) failed");
DBUG_EXIT(E_DEL);
}
else
PATH_TRACE;
caught_sig = 0;

SET_TIMEOUT(WAITTIME/2)

(void) tcflush(tty_fildes, TCIOFLUSH);
if (alrm_flag == 0)
(void) pause();

CLEAR_ALARM


if (caught_sig != SIGTTOU)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("SIGTTOU not received");
}
else
PATH_TRACE;

if (testfail != 0)
DBUG_EXIT(NOK);

if (pathok == 3)
DBUG_EXIT(PATH_OK);

DBUG_VOID_RETURN;
}

private void
test5()
{
int pathok = 0;

DBUG_ENTER("test5");

if (sysconf(_SC_JOB_CONTROL) == -1)
{
xx_rpt(UNSUPPORTED);
in_rpt("_POSIX_JOB_CONTROL not defined");
DBUG_VOID_RETURN;
}
else
PATH_TRACE;

globok = 0;
if ( ! set_tostop(USE_TTY) ) {
xx_rpt(DELETION);
in_rpt("tcsetattr(TCSANOW) to set TOSTOP failed on VSX_TERMIOS_TTY");
DBUG_VOID_RETURN;
} else
PATH_FUNC_TRACE;

testfail = 0;

globok = 0;
if (tprep(0, USE_TTY) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

switch (cppair(ch_t5, NULLFN, 2 * WAITTIME, E_DEL|NOK|PATH_OK))
{
case E_DEL:
DBUG_VOID_RETURN;
case NOK:
testfail++; /* reported in child */
break;
case PATH_OK:
PATH_TRACE;
}

globok = 0;
if (tread(USE_TTY) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

if (input != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIOFLUSH) did not discard input");
}
else
PATH_TRACE;

if (buffered && output != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIOFLUSH) did not discard output");
}
else
PATH_TRACE;

if (testfail == 0)
PATH_XS_RPT(7);

DBUG_VOID_RETURN;
}

private void
ch_t5()
{
int ret;
sigset_t set;
struct sigaction sig;
int pathok = 0;

DBUG_ENTER("ch_t5");

if (setpgid((pid_t)0, (pid_t)0) == SYSERROR)
{
xx_rpt(DELETION);
in_rpt("setpgid(0, 0) failed");
DBUG_EXIT(E_DEL);
}
else
PATH_TRACE;

sig.sa_handler = sig_catch;
sig.sa_flags = 0;
(void) sigemptyset(&sig.sa_mask);
if (sigaction(SIGTTOU, &sig, NULLSA) == SYSERROR)
{
xx_rpt(DELETION);
in_rpt("sigaction(SIGTTOU, ...) failed");
DBUG_EXIT(E_DEL);
}
else
PATH_TRACE;

/* block SIGTTOU */

(void) sigemptyset(&set);
(void) sigaddset(&set, SIGTTOU);
if (sigprocmask(SIG_BLOCK, &set, (sigset_t *)0) == SYSERROR)
{
xx_rpt(DELETION);
in_rpt("sigprocmask(SIG_BLOCK, ...) failed");
DBUG_EXIT(E_DEL);
}
else
PATH_TRACE;

globok = 0;
ret = cppair(gch_t5, do_tcflush, WAITTIME, E_SAFAIL|E_GOTSIG);
if (ret == E_SAFAIL)
{
if (testfail++ == 0)
xx_rpt(DELETION);
in_rpt("sigaction(SIGTTOU, ...) failed");
DBUG_EXIT(E_DEL);
}
else if (ret == E_GOTSIG)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("SIGTTOU received by child of calling process");
}
else
PATH_FUNC_TRACE;

if (tc_ret != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIOFLUSH) returned %d, expected 0", tc_ret);
}
else
PATH_TRACE;

/* Check if SIGTTOU is pending? (shouldn't be) */

if (testfail != 0)
DBUG_EXIT(NOK);

if (pathok == 5)
DBUG_EXIT(PATH_OK);

DBUG_VOID_RETURN;
}

private void
gch_t5()
{
struct sigaction sig;

DBUG_ENTER("gch_t5");

sig.sa_handler = sig_catch;
sig.sa_flags = 0;
(void) sigemptyset(&sig.sa_mask);
if (sigaction(SIGTTOU, &sig, NULLSA) == SYSERROR)
DBUG_EXIT(E_SAFAIL);

caught_sig = 0;

(void) sleep(WAITTIME/2); /* pause for signal */

if (caught_sig == SIGTTOU)
DBUG_EXIT(E_GOTSIG);

DBUG_VOID_RETURN;
}

private void
test6()
{
int pathok = 0;

DBUG_ENTER("test6");

if (sysconf(_SC_JOB_CONTROL) == -1)
{
xx_rpt(UNSUPPORTED);
in_rpt("_POSIX_JOB_CONTROL not defined");
DBUG_VOID_RETURN;
}
else
PATH_TRACE;

globok = 0;
if ( ! set_tostop(USE_TTY) ) {
xx_rpt(DELETION);
in_rpt("tcsetattr(TCSANOW) to set TOSTOP failed on VSX_TERMIOS_TTY");
DBUG_VOID_RETURN;
} else
PATH_FUNC_TRACE;

testfail = 0;

globok = 0;
if (tprep(0, USE_TTY) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

globok = 0;
switch (cppair(ch_t6, NULLFN, 2 * WAITTIME, E_DEL|NOK|PATH_OK))
{
case E_DEL:
DBUG_VOID_RETURN;
case NOK:
testfail++; /* reported in child */
break;
case PATH_OK:
PATH_TRACE;
}

globok = 0;
if (tread(USE_TTY) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

if (input != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIOFLUSH) did not discard input");
}
else
PATH_TRACE;

if (buffered && output != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIOFLUSH) did not discard output");
}
else
PATH_TRACE;

if (testfail == 0)
PATH_XS_RPT(7);

DBUG_VOID_RETURN;
}

private void
ch_t6()
{
int ret;
struct sigaction sig;
int pathok = 0;

DBUG_ENTER("ch_t6");

if (setpgid((pid_t)0, (pid_t)0) == SYSERROR)
{
xx_rpt(DELETION);
in_rpt("setpgid(0, 0) failed");
DBUG_EXIT(E_DEL);
}
else
PATH_TRACE;

sig.sa_handler = SIG_IGN;
sig.sa_flags = 0;
(void) sigemptyset(&sig.sa_mask);
if (sigaction(SIGTTOU, &sig, NULLSA) == SYSERROR)
{
xx_rpt(DELETION);
in_rpt("sigaction(SIGTTOU, ...) failed");
DBUG_EXIT(E_DEL);
}
else
PATH_TRACE;

globok = 0;
ret = cppair(gch_t6, do_tcflush, WAITTIME, E_SAFAIL|E_GOTSIG);
if (ret == E_SAFAIL)
{
if (testfail++ == 0)
xx_rpt(DELETION);
in_rpt("sigaction(SIGTTOU, ...) failed");
DBUG_EXIT(E_DEL);
}
else if (ret == E_GOTSIG)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("SIGTTOU received by child of calling process");
}
else
PATH_FUNC_TRACE;

if (tc_ret != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIOFLUSH) returned %d, expected 0", tc_ret);
}
else
PATH_TRACE;

if (testfail != 0)
DBUG_EXIT(NOK);

if (pathok == 4)
DBUG_EXIT(PATH_OK);

DBUG_VOID_RETURN;
}

private void
gch_t6()
{
struct sigaction sig;

DBUG_ENTER("gch_t6");

sig.sa_handler = sig_catch;
sig.sa_flags = 0;
(void) sigemptyset(&sig.sa_mask);
if (sigaction(SIGTTOU, &sig, NULLSA) == SYSERROR)
DBUG_EXIT(E_SAFAIL);

caught_sig = 0;

(void) sleep(WAITTIME/2); /* pause for signal */

if (caught_sig == SIGTTOU)
DBUG_EXIT(E_GOTSIG);

DBUG_VOID_RETURN;
}

private void
test7()
{
int rval, err;
int pathok = 0;

DBUG_ENTER("test7");

testfail = 0;

if ((rval = tcflush(-1, TCIOFLUSH)) != -1)
{
xx_rpt(FAILURE);
in_rpt("tcflush(-1, TCIOFLUSH) returned %d, expected -1", rval);
}
else if (errno != EBADF)
{
err = errno;
xx_rpt(FAILURE);
in_rpt("tcflush(-1, TCIOFLUSH) gave errno %d (%s), expected EBADF",
err, errname(err));
}
else
{
PATH_TRACE;
PATH_XS_RPT(1);
}

DBUG_VOID_RETURN;
}

private void
test8()
{
int rval, err;
int pathok = 0;

DBUG_ENTER("test8");

testfail = 0;

globok = 0;
if ( ! set_tostop(USE_TTY) ) {
xx_rpt(DELETION);
in_rpt("tcsetattr(TCSANOW) to set TOSTOP failed on VSX_TERMIOS_TTY");
DBUG_VOID_RETURN;
} else
PATH_FUNC_TRACE;

if ((rval = tcflush(tty_fildes, -1)) != -1)
{
xx_rpt(FAILURE);
in_rpt("tcflush(tty_fildes, -1) returned %d, expected -1", rval);
}
else if (errno != EINVAL)
{
err = errno;
xx_rpt(FAILURE);
in_rpt("tcflush(tty_fildes, -1) gave errno %d (%s), expected EINVAL",
err, errname(err));
}
else
{
PATH_TRACE;
PATH_XS_RPT(2);
}

DBUG_VOID_RETURN;
}

private void
test9()
{
int fd, rval, err;
int pathok = 0;

DBUG_ENTER("test9");

testfail = 0;

fd = creat(tfile, MODEANY);
if (fd == SYSERROR)
{
xx_rpt(DELETION);
in_rpt("could not create file %s", tfile);
DBUG_VOID_RETURN;
}
else
PATH_TRACE;

if ((rval = tcflush(fd, TCIOFLUSH)) != -1)
{
xx_rpt(FAILURE);
in_rpt("tcflush(fd, TCIOFLUSH) returned %d, expected -1", rval);
in_rpt("where fd was open to a plain file");
}
else if (errno != ENOTTY)
{
err = errno;
xx_rpt(FAILURE);
in_rpt("tcflush(fd, TCIOFLUSH) gave errno %d (%s), expected ENOTTY",
err, errname(err));
in_rpt("where fd was open to a plain file");
}
else
{
PATH_TRACE;
PATH_XS_RPT(2);
}

(void) close(fd);
(void) unlink(tfile);

DBUG_VOID_RETURN;
}

private void
test10()
{
int pathok = 0;

DBUG_ENTER("test10");

if (sysconf(_SC_JOB_CONTROL) == -1)
{
xx_rpt(UNSUPPORTED);
in_rpt("_POSIX_JOB_CONTROL not defined");
DBUG_VOID_RETURN;
}
else
PATH_TRACE;

globok = 0;
if ( ! set_tostop(USE_LOOP) ) {
xx_rpt(DELETION);
in_rpt("tcsetattr(TCSANOW) to set TOSTOP failed on VSX_TERMIOS_LOOP");
DBUG_VOID_RETURN;
} else
PATH_FUNC_TRACE;

globok = 0;
if ( ! set_tostop(USE_TTY) ) {
xx_rpt(DELETION);
in_rpt("tcsetattr(TCSANOW) to set TOSTOP failed on VSX_TERMIOS_TTY");
DBUG_VOID_RETURN;
} else
PATH_FUNC_TRACE;

testfail = 0;

globok = 0;
if (tprep(0, USE_LOOP) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

globok = 0;
switch (cppair(ch_t10, NULLFN, 2 * WAITTIME, E_DEL|NOK|PATH_OK))
{
case E_DEL:
DBUG_VOID_RETURN;
case NOK:
testfail++; /* reported in child */
break;
case PATH_OK:
PATH_TRACE;
}

globok = 0;
if (tread(USE_LOOP) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

if (input != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIOFLUSH) did not discard input");
}
else
PATH_TRACE;

if (buffered && output != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIOFLUSH) did not discard output");
}
else
PATH_TRACE;

if (testfail == 0)
PATH_XS_RPT(8);

DBUG_VOID_RETURN;
}

private void
ch_t10()
{
int ret;
struct sigaction sig;
int pathok = 0;

DBUG_ENTER("ch_t10");

if (setpgid((pid_t)0, (pid_t)0) == SYSERROR)
{
xx_rpt(DELETION);
in_rpt("setpgid(0, 0) failed");
DBUG_EXIT(E_DEL);
}
else
PATH_TRACE;

sig.sa_handler = SIG_IGN;
sig.sa_flags = 0;
(void) sigemptyset(&sig.sa_mask);
if (sigaction(SIGTTOU, &sig, NULLSA) == SYSERROR)
{
xx_rpt(DELETION);
in_rpt("sigaction(SIGTTOU, ...) failed");
DBUG_EXIT(E_DEL);
}
else
PATH_TRACE;

globok = 0;
ret = cppair(gch_t10, do_looptcflush, WAITTIME, E_SAFAIL|E_GOTSIG);
if (ret == E_SAFAIL)
{
if (testfail++ == 0)
xx_rpt(DELETION);
in_rpt("sigaction(SIGTTOU, ...) failed");
DBUG_EXIT(E_DEL);
}
else if (ret == E_GOTSIG)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("SIGTTOU received by child of calling process");
}
else
PATH_FUNC_TRACE;

if (tc_ret != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIOFLUSH) returned %d, expected 0", tc_ret);
}
else
PATH_TRACE;

if (testfail != 0)
DBUG_EXIT(NOK);

if (pathok == 4)
DBUG_EXIT(PATH_OK);

DBUG_VOID_RETURN;
}

private void
gch_t10()
{
struct sigaction sig;

DBUG_ENTER("gch_t10");

sig.sa_handler = sig_catch;
sig.sa_flags = 0;
(void) sigemptyset(&sig.sa_mask);
if (sigaction(SIGTTOU, &sig, NULLSA) == SYSERROR)
DBUG_EXIT(E_SAFAIL);

caught_sig = 0;

(void) sleep(WAITTIME/2); /* pause for signal */

if (caught_sig == SIGTTOU)
DBUG_EXIT(E_GOTSIG);

DBUG_VOID_RETURN;
}

private void
test11()
{
int rval, err;
int pathok = 0;

DBUG_ENTER("test11");

testfail = 0;

globok = 0;
if (tprep(0, USE_LOOP) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

globok = 0;
if ( ! set_tostop(USE_LOOP) ) {
xx_rpt(DELETION);
in_rpt("tcsetattr(TCSANOW) to set TOSTOP failed on VSX_TERMIOS_LOOP");
DBUG_VOID_RETURN;
} else
PATH_FUNC_TRACE;

globok = 0;
if ( ! set_tostop(USE_TTY) ) {
xx_rpt(DELETION);
in_rpt("tcsetattr(TCSANOW) to set TOSTOP failed on VSX_TERMIOS_TTY");
DBUG_VOID_RETURN;
} else
PATH_FUNC_TRACE;

SET_TIMEOUT(WAITTIME)

rval = tcflush(loop_fildes, TCIFLUSH);
err = errno;

CLEAR_ALARM

if (rval != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIFLUSH) returned %d, expected 0", rval);
in_rpt("errno was set to %d (%s)", err, errname(err));
}
else
PATH_TRACE;

globok = 0;
if (tread(USE_LOOP) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

if (output < BUFLEN)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIFLUSH) discarded output (non-canonical mode)");
}
else
PATH_TRACE;

if (input != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIFLUSH) did not discard input (non-canonical mode)");
}
else
PATH_TRACE;

/* repeat for canonical mode */

globok = 0;
if (tprep(1, USE_LOOP) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

SET_TIMEOUT(WAITTIME)

rval = tcflush(loop_fildes, TCIFLUSH);
err = errno;

CLEAR_ALARM

if (rval != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIFLUSH) returned %d, expected 0", rval);
in_rpt("errno was set to %d (%s)", err, errname(err));
}
else
PATH_TRACE;

globok = 0;
if (tread(USE_LOOP) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

if (output < BUFLEN)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIFLUSH) discarded output (canonical mode)");
}
else
PATH_TRACE;

if (input != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIFLUSH) did not discard input (canonical mode)");
}
else
PATH_TRACE;

if (testfail == 0)
PATH_XS_RPT(12);

DBUG_VOID_RETURN;
}

private void
test12()
{
int pathok = 0;

DBUG_ENTER("test12");

if (sysconf(_SC_JOB_CONTROL) == -1)
{
xx_rpt(UNSUPPORTED);
in_rpt("_POSIX_JOB_CONTROL not defined");
DBUG_VOID_RETURN;
}
else
PATH_TRACE;

globok = 0;
if ( ! set_tostop(USE_LOOP) ) {
xx_rpt(DELETION);
in_rpt("tcsetattr(TCSANOW) to set TOSTOP failed on VSX_TERMIOS_LOOP");
DBUG_VOID_RETURN;
} else
PATH_FUNC_TRACE;

globok = 0;
if ( ! set_tostop(USE_TTY) ) {
xx_rpt(DELETION);
in_rpt("tcsetattr(TCSANOW) to set TOSTOP failed on VSX_TERMIOS_TTY");
DBUG_VOID_RETURN;
} else
PATH_FUNC_TRACE;

testfail = 0;

globok = 0;
do_test12();
PATH_FUNC_TRACE;

if (testfail != 0) {
in_rpt("when TOSTOP was set");
} else {

globok = 0;
if ( ! clear_tostop(USE_LOOP) ) {
xx_rpt(DELETION);
in_rpt("tcsetattr(TCSANOW) to clear TOSTOP failed on VSX_TERMIOS_LOOP");
DBUG_VOID_RETURN;
} else
PATH_FUNC_TRACE;

globok = 0;
if ( ! clear_tostop(USE_TTY) ) {
xx_rpt(DELETION);
in_rpt("tcsetattr(TCSANOW) to clear TOSTOP failed on VSX_TERMIOS_TTY");
DBUG_VOID_RETURN;
} else
PATH_FUNC_TRACE;

globok = 0;
do_test12();
PATH_FUNC_TRACE;

if (testfail != 0) {
in_rpt("when TOSTOP was clear");
} else
PATH_TRACE;
}


if (testfail == 0)
PATH_XS_RPT(8);


DBUG_VOID_RETURN;
}

private
void
do_test12()
{
int pathok = 0;

DBUG_ENTER("do_test12");

globok = 0;
if (tprep(0, USE_LOOP) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

switch (cppair(ch_t12, NULLFN, WAITTIME, E_DEL|NOK|PATH_OK))
{
case E_DEL:
DBUG_VOID_RETURN;
case NOK:
testfail++; /* reported in child */
break;
case PATH_OK:
PATH_TRACE;
}

globok = 0;
if (tread(USE_LOOP) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

if (input != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIOFLUSH) did not discard input");
}
else
PATH_TRACE;

if (buffered && output != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIOFLUSH) did not discard output");
}
else
PATH_TRACE;

globok=0;
if (testfail == 0)
PATH_FUNC_RPT(5);

DBUG_VOID_RETURN;
}

private void
ch_t12()
{
struct sigaction sig;
int pathok = 0;

DBUG_ENTER("ch_t12");

if (setpgid((pid_t)0, (pid_t)0) == SYSERROR)
{
xx_rpt(DELETION);
in_rpt("setpgid(0, 0) failed");
DBUG_EXIT(E_DEL);
}
else
PATH_TRACE;

sig.sa_handler = sig_catch;
sig.sa_flags = 0;
(void) sigemptyset(&sig.sa_mask);
if (sigaction(SIGTTOU, &sig, NULLSA) == SYSERROR)
{
xx_rpt(DELETION);
in_rpt("sigaction(SIGTTOU, ...) failed");
DBUG_EXIT(E_DEL);
}
else
PATH_TRACE;
caught_sig = 0;

SET_TIMEOUT(WAITTIME/2)

(void) tcflush(loop_fildes, TCIOFLUSH);
if (alrm_flag == 0)
(void) pause();

CLEAR_ALARM


if (caught_sig == SIGTTOU)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("SIGTTOU was received");
}
else
PATH_TRACE;

if (testfail != 0)
DBUG_EXIT(NOK);

if (pathok == 3)
DBUG_EXIT(PATH_OK);

DBUG_VOID_RETURN;
}

private void
test13()
{
int pathok = 0;

DBUG_ENTER("test13");

if (sysconf(_SC_JOB_CONTROL) == -1)
{
xx_rpt(UNSUPPORTED);
in_rpt("_POSIX_JOB_CONTROL not defined");
DBUG_VOID_RETURN;
}
else
PATH_TRACE;

globok = 0;
if ( ! set_tostop(USE_LOOP) ) {
xx_rpt(DELETION);
in_rpt("tcsetattr(TCSANOW) to set TOSTOP failed on VSX_TERMIOS_LOOP");
DBUG_VOID_RETURN;
} else
PATH_FUNC_TRACE;

globok = 0;
if ( ! set_tostop(USE_TTY) ) {
xx_rpt(DELETION);
in_rpt("tcsetattr(TCSANOW) to set TOSTOP failed on VSX_TERMIOS_TTY");
DBUG_VOID_RETURN;
} else
PATH_FUNC_TRACE;

testfail = 0;

globok = 0;
if (tprep(0, USE_LOOP) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

switch (cppair(ch_t13, NULLFN, 2 * WAITTIME, E_DEL|NOK|PATH_OK))
{
case E_DEL:
DBUG_VOID_RETURN;
case NOK:
testfail++; /* reported in child */
break;
case PATH_OK:
PATH_TRACE;
}

globok = 0;
if (tread(USE_LOOP) != 0)
{
DBUG_VOID_RETURN;
}
else
PATH_FUNC_TRACE;

if (input != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIOFLUSH) did not discard input");
}
else
PATH_TRACE;

if (buffered && output != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIOFLUSH) did not discard output");
}
else
PATH_TRACE;

if (testfail == 0)
PATH_XS_RPT(8);

DBUG_VOID_RETURN;
}

private void
ch_t13()
{
int ret;
sigset_t set;
struct sigaction sig;
int pathok = 0;

DBUG_ENTER("ch_t13");

if (setpgid((pid_t)0, (pid_t)0) == SYSERROR)
{
xx_rpt(DELETION);
in_rpt("setpgid(0, 0) failed");
DBUG_EXIT(E_DEL);
}
else
PATH_TRACE;

sig.sa_handler = sig_catch;
sig.sa_flags = 0;
(void) sigemptyset(&sig.sa_mask);
if (sigaction(SIGTTOU, &sig, NULLSA) == SYSERROR)
{
xx_rpt(DELETION);
in_rpt("sigaction(SIGTTOU, ...) failed");
DBUG_EXIT(E_DEL);
}
else
PATH_TRACE;

/* block SIGTTOU */

(void) sigemptyset(&set);
(void) sigaddset(&set, SIGTTOU);
if (sigprocmask(SIG_BLOCK, &set, (sigset_t *)0) == SYSERROR)
{
xx_rpt(DELETION);
in_rpt("sigprocmask(SIG_BLOCK, ...) failed");
DBUG_EXIT(E_DEL);
}
else
PATH_TRACE;

globok = 0;
ret = cppair(gch_t13, do_looptcflush, WAITTIME, E_SAFAIL|E_GOTSIG);
if (ret == E_SAFAIL)
{
if (testfail++ == 0)
xx_rpt(DELETION);
in_rpt("sigaction(SIGTTOU, ...) failed");
DBUG_EXIT(E_DEL);
}
else if (ret == E_GOTSIG)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("SIGTTOU received by child of calling process");
}
else
PATH_FUNC_TRACE;

if (tc_ret != 0)
{
if (testfail++ == 0)
xx_rpt(FAILURE);
in_rpt("tcflush(TCIOFLUSH) returned %d, expected 0", tc_ret);
}
else
PATH_TRACE;

/* Check if SIGTTOU is pending? (shouldn't be) */

if (testfail != 0)
DBUG_EXIT(NOK);

if (pathok == 5)
DBUG_EXIT(PATH_OK);

DBUG_VOID_RETURN;
}

private void
gch_t13()
{
struct sigaction sig;

DBUG_ENTER("gch_t13");

sig.sa_handler = sig_catch;
sig.sa_flags = 0;
(void) sigemptyset(&sig.sa_mask);
if (sigaction(SIGTTOU, &sig, NULLSA) == SYSERROR)
DBUG_EXIT(E_SAFAIL);

caught_sig = 0;

(void) sleep(WAITTIME/2); /* pause for signal */

if (caught_sig == SIGTTOU)
DBUG_EXIT(E_GOTSIG);

DBUG_VOID_RETURN;
}


private void
test14()
{
DBUG_ENTER("test14");

do_fnoeio("w", -1, t14io, t14rpt);

DBUG_VOID_RETURN;
}

private int
t14io(fp)
FILE *fp;
{
globok = 1;
return tcflush(fileno(fp), TCOFLUSH);
}

private
void
t14rpt(ret, err)
int ret, err;
{
int pathok = 0;

DBUG_ENTER("t14rpt");

if(ret != 0)
{
xx_rpt(FAILURE);
in_rpt("tcflush() from orphaned background process group did not give correct results");
if (ret != 0)
in_rpt("expected return: 0, actual: %d", ret);
if (ret == SYSERROR)
in_rpt("expected no error. actual errno: %d(%s)",
err, errname(err));
} else {
PATH_TRACE;
PATH_XS_RPT(1);
}

DBUG_VOID_RETURN;
}

2013-09-25 18:02:43

by Oleg Nesterov

[permalink] [raw]
Subject: Re: v3.10 breaks T.tcflush (Was: tty: disassociate_ctty() sends the extra SIGCONT)

On 09/25, Oleg Nesterov wrote:
>
> See lsb-test-core/lts_vsx-pcts/tset/POSIX.os/devclass/tcflush/tcflush.c
> attached below. test4() fails with
>
> SIGTTOU not received
> tcflush(TCIOFLUSH) action was performed
> when TOSTOP was set

Just in case... I do not really know if it was broken by 3.10 or earlier.

And of course, I do not know who is actually wrong, kernel or lsb.

Oleg.

2013-09-26 00:07:05

by Peter Hurley

[permalink] [raw]
Subject: Re: v3.10 breaks T.tcflush (Was: tty: disassociate_ctty() sends the extra SIGCONT)

On 09/25/2013 12:21 PM, Oleg Nesterov wrote:
> Peter, thanks for your explanations about tty_old_pgrp/etc,
> I'll try to reply later. I need to read your email carefully.

Ok.

If you're interested, I highly recommend reading Chapter 34
("Process Groups, Sessions, and Job Control") of Michael Kerrisk's
book, The Linux Programming Interface.

> FYI, LSB tests report another failure starting from 3.10.
>
> See lsb-test-core/lts_vsx-pcts/tset/POSIX.os/devclass/tcflush/tcflush.c
> attached below. test4() fails with
>
> SIGTTOU not received
> tcflush(TCIOFLUSH) action was performed
> when TOSTOP was set
>
> I'll try to investigate, but let me repeat I know absolutely nothing
> about drivers/tty/, termios, etc. Perhaps you can take a look? So far I
> didn't even look into the attached code, I have no idea what it does.

Thanks for the report. I looked into this and found another regression.
Patch coming.

Looks like I'll be integrating LSB into my testbench.

Regards,
Peter Hurley

2013-09-26 00:13:41

by Peter Hurley

[permalink] [raw]
Subject: [PATCH] tty: Fix SIGTTOU not sent with tcflush()

Commit 'e7f3880cd9b98c5bf9391ae7acdec82b75403776'
tty: Fix recursive deadlock in tty_perform_flush()
introduced a regression where tcflush() does not generate
SIGTTOU for background process groups.

Make sure ioctl(TCFLSH) calls tty_check_change() when
invoked from the line discipline.

Cc: [email protected] # v3.10+
Reported-by: Oleg Nesterov <[email protected]>
Signed-off-by: Peter Hurley <[email protected]>
---
drivers/tty/tty_ioctl.c | 3 +++
1 file changed, 3 insertions(+)

diff --git a/drivers/tty/tty_ioctl.c b/drivers/tty/tty_ioctl.c
index 3500d41..088b4ca 100644
--- a/drivers/tty/tty_ioctl.c
+++ b/drivers/tty/tty_ioctl.c
@@ -1201,6 +1201,9 @@ int n_tty_ioctl_helper(struct tty_struct *tty, struct file *file,
}
return 0;
case TCFLSH:
+ retval = tty_check_change(tty);
+ if (retval)
+ return retval;
return __tty_perform_flush(tty, arg);
default:
/* Try the mode commands */
--
1.8.1.2

2013-09-26 00:24:17

by Greg Kroah-Hartman

[permalink] [raw]
Subject: Re: [PATCH] tty: Fix SIGTTOU not sent with tcflush()

On Wed, Sep 25, 2013 at 08:13:04PM -0400, Peter Hurley wrote:
> Commit 'e7f3880cd9b98c5bf9391ae7acdec82b75403776'
> tty: Fix recursive deadlock in tty_perform_flush()
> introduced a regression where tcflush() does not generate
> SIGTTOU for background process groups.
>
> Make sure ioctl(TCFLSH) calls tty_check_change() when
> invoked from the line discipline.
>
> Cc: [email protected] # v3.10+
> Reported-by: Oleg Nesterov <[email protected]>
> Signed-off-by: Peter Hurley <[email protected]>
> ---
> drivers/tty/tty_ioctl.c | 3 +++
> 1 file changed, 3 insertions(+)

Thanks for tracking this down, I'll queue it up tomorrow.

greg k-h

2013-09-26 16:43:06

by Oleg Nesterov

[permalink] [raw]
Subject: Re: [PATCH] tty: Fix SIGTTOU not sent with tcflush()

Thanks Peter.

Well. I'am afraid my testing was wrong, because Karel reports
it fixes the problem...

But. I applied this patch to my 3.11 tree (last commit is bff157b3a)
which also has the additional patch (03e12617 "tty: disassociate_ctty()
sends the extra SIGCONT"), and

TET_CONFIG=CFG TET_ROOT=. ./T.tcflush 4

still fails... T.tcflush was compiled on another (Karel's) machine,
perhaps there is something in libc, I do not know.

So let me ask just in case, I assume the fix below doesn't depend on
other changes I could miss?

I'll retest after git-pull and report...

On 09/25, Peter Hurley wrote:
>
> Commit 'e7f3880cd9b98c5bf9391ae7acdec82b75403776'
> tty: Fix recursive deadlock in tty_perform_flush()
> introduced a regression where tcflush() does not generate
> SIGTTOU for background process groups.
>
> Make sure ioctl(TCFLSH) calls tty_check_change() when
> invoked from the line discipline.
>
> Cc: [email protected] # v3.10+
> Reported-by: Oleg Nesterov <[email protected]>

Actually Reported-by: Karel Srot <[email protected]>

my fault, forgot to mention this.

> Signed-off-by: Peter Hurley <[email protected]>
> ---
> drivers/tty/tty_ioctl.c | 3 +++
> 1 file changed, 3 insertions(+)
>
> diff --git a/drivers/tty/tty_ioctl.c b/drivers/tty/tty_ioctl.c
> index 3500d41..088b4ca 100644
> --- a/drivers/tty/tty_ioctl.c
> +++ b/drivers/tty/tty_ioctl.c
> @@ -1201,6 +1201,9 @@ int n_tty_ioctl_helper(struct tty_struct *tty, struct file *file,
> }
> return 0;
> case TCFLSH:
> + retval = tty_check_change(tty);
> + if (retval)
> + return retval;
> return __tty_perform_flush(tty, arg);
> default:
> /* Try the mode commands */
> --
> 1.8.1.2
>

2013-09-26 19:17:09

by Oleg Nesterov

[permalink] [raw]
Subject: Re: [PATCH] tty: Fix SIGTTOU not sent with tcflush()

On 09/26, Oleg Nesterov wrote:
>
> Thanks Peter.
>
> Well. I'am afraid my testing was wrong, because Karel reports
> it fixes the problem...
>
> But. I applied this patch to my 3.11 tree (last commit is bff157b3a)
> which also has the additional patch (03e12617 "tty: disassociate_ctty()
> sends the extra SIGCONT"), and
>
> TET_CONFIG=CFG TET_ROOT=. ./T.tcflush 4
>
> still fails... T.tcflush was compiled on another (Karel's) machine,
> perhaps there is something in libc, I do not know.
>
> So let me ask just in case, I assume the fix below doesn't depend on
> other changes I could miss?
>
> I'll retest after git-pull and report...

Still fails under the Linus's tree + this patch.

However!!!

Tested-by: Oleg Nesterov <[email protected]>




It turns out, T.tcflush doesn't expect it can start as a process group
leader. In this case setsid() fails, then tty_open() doesn't set
signal->tty, and thus this patch makes no difference: tty_check_change()
fails because tty doesn't match signal->tty.

And indeed, this test passes if you run it under strace, or simply do

$ TET_CONFIG=CFG TET_ROOT=. perl -e "system './T.tcflush 4'"

And damn, the fact it doesn't fail under strace doesn't allow you to
see that setsid() fails ;)

This is probably explains why Karel reported success, perhaps he
didn't run this test individually.

Thanks again Peter. Perhaps my analysis was wrong (and I do not see
setsid() in the sources), I do not really care. but perhaps tcflush.c
should be updated.

Oleg.

2013-09-26 21:18:34

by Peter Hurley

[permalink] [raw]
Subject: Re: [PATCH] tty: Fix SIGTTOU not sent with tcflush()

On 09/26/2013 03:10 PM, Oleg Nesterov wrote:
> On 09/26, Oleg Nesterov wrote:
>>
>> Thanks Peter.
>>
>> Well. I'am afraid my testing was wrong, because Karel reports
>> it fixes the problem...
>>
>> But. I applied this patch to my 3.11 tree (last commit is bff157b3a)
>> which also has the additional patch (03e12617 "tty: disassociate_ctty()
>> sends the extra SIGCONT"), and
>>
>> TET_CONFIG=CFG TET_ROOT=. ./T.tcflush 4
>>
>> still fails... T.tcflush was compiled on another (Karel's) machine,
>> perhaps there is something in libc, I do not know.
>>
>> So let me ask just in case, I assume the fix below doesn't depend on
>> other changes I could miss?
>>
>> I'll retest after git-pull and report...
>
> Still fails under the Linus's tree + this patch.
>
> However!!!
>
> Tested-by: Oleg Nesterov <[email protected]>

Greg picked this patch up yesterday evening so it went in as-is.


> It turns out, T.tcflush doesn't expect it can start as a process group
> leader. In this case setsid() fails, then tty_open() doesn't set
> signal->tty, and thus this patch makes no difference: tty_check_change()
> fails because tty doesn't match signal->tty.
>
> And indeed, this test passes if you run it under strace, or simply do
>
> $ TET_CONFIG=CFG TET_ROOT=. perl -e "system './T.tcflush 4'"
>
> And damn, the fact it doesn't fail under strace doesn't allow you to
> see that setsid() fails ;)

I wondered if the test was designed for standalone.

FWIW, I looked at tcflush.c to see what test4() was doing,
wrote my own test vector to confirm the problem, instrumented my
test vector, realized what the problem was, wrote the patch,
built mainline 3.11 + this patch, re-tested the problem, confirmed
the regression went back to 3.10, prepared the patch and sent
the emails.

It's going to take me some time to figure out how best to use LSB;
all the test instrumentation makes the source hard to work with
directly.


> This is probably explains why Karel reported success, perhaps he
> didn't run this test individually.
>
> Thanks again Peter. Perhaps my analysis was wrong (and I do not see
> setsid() in the sources), I do not really care. but perhaps tcflush.c
> should be updated.

I would agree with your analysis.

Regards,
Peter Hurley

2013-09-27 05:20:17

by Karel Srot

[permalink] [raw]
Subject: Re: [PATCH] tty: Fix SIGTTOU not sent with tcflush()

Hello Oleg,

On Thu, 2013-09-26 at 21:10 +0200, Oleg Nesterov wrote:
> On 09/26, Oleg Nesterov wrote:
> >
> > Thanks Peter.
> >
> > Well. I'am afraid my testing was wrong, because Karel reports
> > it fixes the problem...
> >
> > But. I applied this patch to my 3.11 tree (last commit is bff157b3a)
> > which also has the additional patch (03e12617 "tty: disassociate_ctty()
> > sends the extra SIGCONT"), and
> >
> > TET_CONFIG=CFG TET_ROOT=. ./T.tcflush 4
> >
> > still fails... T.tcflush was compiled on another (Karel's) machine,
> > perhaps there is something in libc, I do not know.
> >
> > So let me ask just in case, I assume the fix below doesn't depend on
> > other changes I could miss?
> >
> > I'll retest after git-pull and report...
>
> Still fails under the Linus's tree + this patch.
>
> However!!!
>
> Tested-by: Oleg Nesterov <[email protected]>
>
>
>
>
> It turns out, T.tcflush doesn't expect it can start as a process group
> leader. In this case setsid() fails, then tty_open() doesn't set
> signal->tty, and thus this patch makes no difference: tty_check_change()
> fails because tty doesn't match signal->tty.
>
> And indeed, this test passes if you run it under strace, or simply do
>
> $ TET_CONFIG=CFG TET_ROOT=. perl -e "system './T.tcflush 4'"
>
> And damn, the fact it doesn't fail under strace doesn't allow you to
> see that setsid() fails ;)
>
> This is probably explains why Karel reported success, perhaps he
> didn't run this test individually.

This is true, I didn't execute this particular test directly but through
the LSB test suite framework.

>
> Thanks again Peter. Perhaps my analysis was wrong (and I do not see
> setsid() in the sources), I do not really care. but perhaps tcflush.c
> should be updated.
>
> Oleg.
>


Karel

--
Karel Srot
Quality Engineer, QE BaseOS Security team

Email: [email protected]
Web: http://www.cz.redhat.com
Red Hat Czech s.r.o., Purkyňova 99/71, 612 45, Brno, Czech Republic