Skip to contentSkip to conversation

script input redirection / eof handling

DISCUSSION Phillip Susi 2 participants 9 messages Oct 31, 2013 – Dec 3, 2013 Util-Linux package

lore
Share & tools
lore
#0 lore raw

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

I came across an old bug filed in debian about script being redirected from a file misbehaving. It seems that when the master ( input ) process hits eof, it exits immediately, orphaning the child ( output ) and user processes.

I believe the correct thing to do is signal the eof to the user process and wait for the children to exit, but I'm not sure how to make a pty signal eof.

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2.0.17 (MingW32)
Comment: Using GnuPG with Thunderbird - http://www.enigmail.net/

iQEcBAEBAgAGBQJScmdWAAoJEJrBOlT6nu75JIQH/1Eqt6gsG+vKGc8xpybIE6F7 UGgo8sLyn++PE3U6MZQcsWj2nHZ6p76uHOp4Kn1sk8xrb2zetueQTF1idtgJO7nS PKhwbfInD3ocSuEQB1HygMmKTKuXT9qYCBj18AT9ZQp7eQ5ocz+nvNnGHQuR4KPR /CsI0D0/BnTO32ayh6qgE1HtxD0sUY4K1Qw3MyyChINwFAU9q8p3Y9Lz5RqQTTZk 2j3kc6jDdNaGy1b6w1exhEMrhs7zC1LRvz5GhqM+ODVAN/YiMh4eKYH9a4OrYXru l8Df0gDWNRrQhqrJxvOMKph/DZO1sZEZP3ZG/kOXak/cpbHVy0MkGepP0qId91g= =ZaJO
-----END PGP SIGNATURE-----

maintainer
#1 lore raw
Quoted 5 lines · Phillip Susi

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

I came across an old bug filed in debian about script being redirected from a file misbehaving.

Do you mean something like:

echo "ps uf" | script

.. if yes, then it's pretty unexpected usage. The code expects that stdin is terminal. (For example it copy the current terminal attributes the new pseudo-terminal.)

Unfortunately it does not check tcgetatt() return code so the problem is not visible. I'll fix it.

Quoted 3 lines

It seems that when the master ( input )
process hits eof, it exits immediately, orphaning the child ( output ) and user processes.

Yep, I think it would be enough to add

       kill(0, SIGTERM);

before done() in doinput() to avoid orphans.

Karel

Signature · 2 lines
 Karel Zak  <[email protected]>
 http://karelzak.blogspot.com
maintainer
#2 lore raw
Quoted 9 lines · Karel Zak
Quoted 5 lines · Phillip Susi

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

I came across an old bug filed in debian about script being redirected from a file misbehaving.

Do you mean something like:

Quoted 8 lines

echo "ps uf" | script

.. if yes, then it's pretty unexpected usage. The code expects that stdin is terminal. (For example it copy the current terminal attributes the new pseudo-terminal.)

Unfortunately it does not check tcgetatt() return code so the problem is not visible. I'll fix it.

Note, that if you want to support this scenario (stdin is not a terminal) then send a patch ;-)

I guess you have to initialize the terminal (see inlude/ttyutils.h, reset_virtual_console()) and use this dummy struct termios instead of the current 'tt'.

Quoted 5 lines

Yep, I think it would be enough to add

       kill(0, SIGTERM);

before done() in doinput() to avoid orphans.

This is probably not the best solution (because shell will print "Terminated" etc.), but it's definitely better than orphans.

Karel

Signature · 2 lines
 Karel Zak  <[email protected]>
 http://karelzak.blogspot.com
#3 lore raw

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Quoted 2 lines · Karel Zak

Note, that if you want to support this scenario (stdin is not a terminal) then send a patch ;-)

It seems to work fine up until it hits eof, which can also happen on a tty and you'll get the same bad behavior, so I think refusing to run on a non tty isn't a proper fix.

Quoted 5 lines
Quoted 5 lines

Yep, I think it would be enough to add

kill(0, SIGTERM);

before done() in doinput() to avoid orphans.

You don't want to just make sure the children die, you want to make sure that the child sees the eof and capture any final output it may have.

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2.0.17 (MingW32)
Comment: Using GnuPG with Thunderbird - http://www.enigmail.net/

iQEcBAEBAgAGBQJSnJz9AAoJEI5FoCIzSKrw6j0IAKZvkym0kJQfWghombuTnV1z 5FCcWPz3Jyo9V96X+fnD3cXpkkPjIM5ZUIBaRJubUYX65AKebXaXw+FkE9YyXsIR Jeegewp5IlSlF3zbZzCSUchI/gK0KCHv1yKe21xD20iGAy9MGApXBJszsOaREbU2 nG9mFYwviFDQnURBfwHxvS7Bf4S+6i1wgKeS58EqSeXpMpMDCimTw1/mv8MLnGdS 0nzCrPZvgRZcc2avYPSWYs8ZLtFgu5rDuJ7J9TfEuiEzGWCqlPXQYF1a6nHvpT0W KSdu+1bzsasdrEXFF6Iai2NvFndg9JXqiLCGfKdELbJayYPjwltOtl41PfalG/w= =2lT4
-----END PGP SIGNATURE-----

maintainer
#4 lore raw
Quoted 8 lines · Phillip Susi

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Quoted 2 lines · Karel Zak

Note, that if you want to support this scenario (stdin is not a terminal) then send a patch ;-)

It seems to work fine up until it hits eof, which can also happen on a

well, it's nasty that the code ignores return codes and it call's tty ioctls/functions for non-tty file descriptors.

Quoted 11 lines

tty and you'll get the same bad behavior, so I think refusing to run on a non tty isn't a proper fix.

Quoted 5 lines
Quoted 5 lines

Yep, I think it would be enough to add

kill(0, SIGTERM);

before done() in doinput() to avoid orphans.

You don't want to just make sure the children die, you want to make sure that the child sees the eof and capture any final output it may have.

never ending orphans suck...

The possible solution (hack) to emulate end-of-file is by write "exit\n" to the master tty. For example patch below works for me (it also make it more robust for non-ttys).

Karel

term-utils/script.c +44 -16
index c18274c..8437ec8 100644
@@ -107,6 +107,7 @@ int	fflg = 0;
 int	qflg = 0;
 int	tflg = 0;
 int	forceflg = 0;
+int	isterm;
 
 int die;
 int resized;
@@ -230,9 +231,6 @@ main(int argc, char **argv) {
 		die_if_link(fname);
 	}
 
-	if (!isatty(STDIN_FILENO))
-		errx(EXIT_FAILURE, _("The stdin is not a terminal."));
-
 	if ((fscript = fopen(fname, aflg ? "a" : "w")) == NULL) {
 		warn(_("cannot open %s"), fname);
 		fail();
@@ -314,11 +312,21 @@ doinput(void) {
 		else if (cc < 0 && errno == EINTR && resized)
 		{
 			/* transmit window change information to the child */
-			ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&win);
-			ioctl(slave, TIOCSWINSZ, (char *)&win);
+			if (isterm) {
+				ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&win);
+				ioctl(slave, TIOCSWINSZ, (char *)&win);
+			}
 			resized = 0;
-		}
-		else
+		} else if (cc == 0 && errno == 0) {
+			/* end-of-file, probably "foo | script" */
+			strcpy(ibuf, "exit\n");
+			if (write(master, ibuf, 5) < 0) {
+				warn (_("write failed"));
+				fail();
+			}
+			pause();		/* wait for SIGCHLD */
+			break;
+		} else
 			break;
 	}
 
@@ -473,6 +481,9 @@ void
 fixtty(void) {
 	struct termios rtt;
 
+	if (!isterm)
+		return;
+
 	rtt = tt;
 	cfmakeraw(&rtt);
 	rtt.c_lflag &= ~ECHO;
@@ -503,7 +514,8 @@ done(void) {
 
 		master = -1;
 	} else {
-		tcsetattr(STDIN_FILENO, TCSADRAIN, &tt);
+		if (isterm)
+			tcsetattr(STDIN_FILENO, TCSADRAIN, &tt);
 		if (!qflg)
 			printf(_("Script done, file is %s\n"), fname);
 #ifdef HAVE_LIBUTEMPTER
@@ -524,9 +536,19 @@ done(void) {
 void
 getmaster(void) {
 #if defined(HAVE_LIBUTIL) && defined(HAVE_PTY_H)
-	tcgetattr(STDIN_FILENO, &tt);
-	ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&win);
-	if (openpty(&master, &slave, NULL, &tt, &win) < 0) {
+	int rc;
+
+	isterm = isatty(STDIN_FILENO);
+
+	if (isterm) {
+		if (tcgetattr(STDIN_FILENO, &tt) != 0)
+			err(EXIT_FAILURE, _("failed to get terminal attributes"));
+		ioctl(STDIN_FILENO, TIOCGWINSZ, (char *) &win);
+		rc = openpty(&master, &slave, NULL, &tt, &win);
+	} else
+		rc = openpty(&master, &slave, NULL, NULL, NULL);
+
+	if (rc < 0) {
 		warn(_("openpty failed"));
 		fail();
 	}
@@ -534,6 +556,8 @@ getmaster(void) {
 	char *pty, *bank, *cp;
 	struct stat stb;
 
+	isterm = isatty(STDIN_FILENO);
+
 	pty = &line[strlen("/dev/ptyp")];
 	for (bank = "pqrs"; *bank; bank++) {
 		line[strlen("/dev/pty")] = *bank;
@@ -552,9 +576,11 @@ getmaster(void) {
 				ok = access(line, R_OK|W_OK) == 0;
 				*tp = 'p';
 				if (ok) {
-					tcgetattr(STDIN_FILENO, &tt);
-					ioctl(STDIN_FILENO, TIOCGWINSZ,
-						(char *)&win);
+					if (isterm) {
+						tcgetattr(STDIN_FILENO, &tt);
+						ioctl(STDIN_FILENO, TIOCGWINSZ,
+								(char *)&win);
+					}
 					return;
 				}
 				close(master);
@@ -577,8 +603,10 @@ getslave(void) {
 		warn(_("cannot open %s"), line);
 		fail();
 	}
-	tcsetattr(slave, TCSANOW, &tt);
-	ioctl(slave, TIOCSWINSZ, (char *)&win);
+	if (isterm) {
+		tcsetattr(slave, TCSANOW, &tt);
+		ioctl(slave, TIOCSWINSZ, (char *)&win);
+	}
 #endif
 	setsid();
 	ioctl(slave, TIOCSCTTY, 0);
#5 lore raw

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Quoted 1 line · Karel Zak

never ending orphans suck...

Yes, but the idea is not to orphan them in the first place and wait for them to terminate instead.

Quoted 3 lines

The possible solution (hack) to emulate end-of-file is by write "exit\n" to the master tty. For example patch below works for me (it also make it more robust for non-ttys).

That only works if the program on the other end knows what "exit" means, which is basically only true if it's a shell. Surely there must be a proper way to signal EOF over a pty? Can you use shutdown() on a pty?

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2.0.17 (MingW32)
Comment: Using GnuPG with Thunderbird - http://www.enigmail.net/

iQEcBAEBAgAGBQJSnMwNAAoJEI5FoCIzSKrwwvYH+wRd0hXZwZKhElWgpLIjWNOO 82ulPzt1Cld085qllaG8VvO0CSN3JERHD5Eo8iB+YIuI32lNO2dGuGw0MwYJ72cn whUEqT7BNcnDCDqhPba/ZYcGJ8mCp+3+58BDth8NPu+nma7d6V0n4sAbqO+w6uCS VZOO9qo2cKYGqs7E2YYWLHBadMGkyLjpDd9j7ZsLxEe6ezb1vFLxSmJQ3CiNLz+i q2okm0GUipkhMAMCC8zuV1GnUf5sJuSx6QiSkIE5Mok9d8HqOtXTt8F7URAa+YVC tBtM7dMIfXEQCJil16hTn+KdgiAtNE4BaGkyevW2kohwmKQgKU51/CHlofWGK1s= =6Ihg
-----END PGP SIGNATURE-----

maintainer
#6 lore raw
Quoted 1 line · Phillip Susi

That only works if the program on the other end knows what "exit"

yep, fingers have been faster than brain.. (I forgot we have --command).

Quoted 3 lines

means, which is basically only true if it's a shell. Surely there must be a proper way to signal EOF over a pty? Can you use shutdown() on a pty?

I'm going to play with that tomorrow.

Karel

Signature · 2 lines
 Karel Zak  <[email protected]>
 http://karelzak.blogspot.com
maintainer
#7 lore raw
In reply to Phillip Susi
Quoted 4 lines · Phillip Susi

That only works if the program on the other end knows what "exit" means, which is basically only true if it's a shell. Surely there must be a proper way to signal EOF over a pty? Can you use shutdown() on a pty?

Oh, now it reminds me... in include/ttyutils.h we have macros to generate tty control chars, for example ^C

			char eof = DEF_EOF;

			if (write(master, &eof, 1) < 0) {
				warn (_("write failed"));
				fail();
			}
            pause();

is what we need.

Karel

Signature · 2 lines
 Karel Zak  <[email protected]>
 http://karelzak.blogspot.com
maintainer
#8 lore raw
Quoted 17 lines · Karel Zak
Quoted 4 lines · Phillip Susi

That only works if the program on the other end knows what "exit" means, which is basically only true if it's a shell. Surely there must be a proper way to signal EOF over a pty? Can you use shutdown() on a pty?

Oh, now it reminds me... in include/ttyutils.h we have macros to generate tty control chars, for example ^C

			char eof = DEF_EOF;

			if (write(master, &eof, 1) < 0) {
				warn (_("write failed"));
				fail();
			}
            pause();
Quoted 1 line

is what we need.

It was more tricky than I have originally expected, but it seems it works. (It seems that it is not enough to send EOF, you have to be sure that someone already listens on slave side.)

Karel

Signature · 2 lines
 Karel Zak  <[email protected]>
 http://karelzak.blogspot.com