Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cloning command `script` and PTY background job problems: terminal messed up

Tags:

c

unix

macos

I'm trying to recode the UNIX command script (as it is on OSX). This is part of an exercise for school to help students learn UNIX APIs. We are only allowed to use system calls, more specifically, only those available on MAN(2) pages on Mac OSX (since that's our OS at school).

I have a 'first version' that kind of works. Running a program such as ls prints the right output to the screen and in an output file.

The problem scenario

I run bash from within the script-clone. First issue is I get the following error: bash: no job control in this shell

I have tried forcing the bash process into foreground with setpgrp and setpgid but that din't change anything so I concluded that was not the problem.

I also tried to understand why the real script command uses cfmakeraw (at least on Linux), as seen here, but I don't get it. The MAN page is not very helpful.

The real script also dup2s STDIN on the slave, as seen here, but when I do that, it seems like input isn't read anymore.

However, the bash still runs, and I can execute commands inside of it.

But if I run vim inside it, and then hit Ctrl-Z to put vim to the background, the terminal is messed up (which does not happen when I'm in my regular terminal).

So I guess I must have done something wrong. I'd appreciate any advice/help.


Here's the source code: https://github.com/conradkleinespel/unix-command-script/tree/2587b07e7a36dc74bf6dff0e82c9fdd33cb40411

You can compile by doing: make (it builds on OSX 10.9, hopefully on Linux as well)

And run by doing: ./ft_script

Don't know it it makes more sense to have all the source code in StackOverflow as it would crowd the page with it. If needed, I can replace the Git link with the source.

like image 434
conradkleinespel Avatar asked Nov 02 '22 00:11

conradkleinespel


1 Answers

I don't use OS X, so I can't directly test your code, but I'm currently writing a toy terminal emulator and had similar troubles.

about "bash: no job control in this shell"

In order to perform job control, a shell needs to be a session leader and the controlling process of its terminal. By default, your program inherits the controlling terminal of your own shell which runs your script program and which is also a session leader. Here is how to make your new slave process a session leader after fork:

/* we don't need the inherited master fd */
close(master);
/* discard the previous controlling tty */
ioctl(0, TIOCNOTTY, 0);
/* replace existing stdin/out/err with the slave pts */
dup2(slave, 0);
dup2(slave, 1);
dup2(slave, 2);
/* discard the extra file descriptor for the slave pts */
close(slave);
/* make the pts our controlling terminal */
ioctl(0, TIOCSCTTY, 0);
/* make a new session */
setsid()

At this point, the forked process has stdin/out/err bound to the new pts, the pts became its controlling terminal, and the process is a session leader. The job control should now work.

about raw tty

When you run a program inside a normal terminal, it looks like this:

(term emulator, master side) <=> /dev/pts/42 <=> (program, slave side)

If you press ^Z, the terminal emulator will write the ascii character 0x1A to the pts. It is a control character, so it won't be sent to the program, but instead the kernel will issue SIGSTP to the program and suspend it. The process of transforming characters into something else is called "line cooking" and has various settings that can be adjusted for each tty.

Now let's look at the situation with script:

term emulator <=> /dev/pts/42 <=> script <=> /dev/pts/43 <=> program

With normal line settings, what happens when you press ^Z? It will be transformed into SIGSTP by /dev/pts/42 and script will be suspended. But that's not what we want, instead we'd like the 0x1A character produced by our ^Z to go as-is through /dev/pts/42, then be passed by script to /dev/pts/43 and only then be transformed into SIGSTP to suspend the program.

This is the reason why the pts between your terminal and script must be configured as "raw", so that all control characters reach the pts between script and the program, as if you were directly working with it.

like image 190
Grapsus Avatar answered Nov 13 '22 06:11

Grapsus