Starting from:
$30

$24

Homework #4 Terminal Multiplexer Solution

Introduction




The goal of this assignment is to become familiar with low-level Unix/POSIX system

calls related to processes, signal handling, files, and I/O redirection.

You will complete a simple terminal multiplexer program that allows several virtual

terminal sessions to be multiplexed onto a single real terminal.

This program, called `ecran`, is inspired by the classic `screen` program,

which was originally written by Oliver Laumann about 1987 and which has evolved

over the years since into [GNU Screen](https://www.gnu.org/software/screen/)

Although this program originated back in the days before there were PCs with

advanced windowing systems, this program and other related programs are still

useful today for managing remote login sessions that can be detached and

reattached without losing the session state. A more recent program along

these lines is [tmux](https://github.com/tmux/tmux/wiki).

You can read general information about terminal multiplexer programs on

[Wikipedia](https://en.wikipedia.org/wiki/Terminal_multiplexer).




### Takeaways




After completing this assignment, you should:




* Understand process execution: forking, executing, and reaping.

* Understand signal handling.

* Understand the use of "dup" to perform I/O redirection.

* Have a more advanced understanding of Unix commands and the command line.

* Have gained experience with C libraries and system calls.

* Have enhanced your C programming abilities.




## Hints and Tips




* We **strongly recommend** that you check the return codes of **all** system calls

and library functions. This will help you catch errors.

* **BEAT UP YOUR OWN CODE!** Use a "monkey at a typewriter" approach to testing it

and make sure that no sequence of operations, no matter how ridiculous it may

seem, can crash the program.

* Your code should **NEVER** crash, and we will deduct points every time your

program crashes during grading. Especially make sure that you have avoided

race conditions involving session termination and reaping that might result

in "flaky" behavior. If you notice odd behavior you don't understand:

**INVESTIGATE**.

* You should use the `debug` macro provided to you in the base code.

That way, when your program is compiled without `-DDEBUG`, all of your debugging

output will vanish, preventing you from losing points due to superfluous output.

It is a little more difficult to debug the program in the present assignment,

due to the fact that it takes over the terminal window on which it is started,

but in the first part of this assignment you will implement a feature that will

probably be helpful in debugging.




:nerd: When writing your program, try to comment as much as possible and stay

consistent with code formatting. Keep your code organized, and don't be afraid

to introduce new source files if/when appropriate.




### Reading Man Pages




This assignment will involve the use of many system calls and library functions

that you probably haven't used before.

As such, it is imperative that you become comfortable looking up function

specifications using the `man` command.




The `man` command stands for "manual" and takes the name of a function or command

(programs) as an argument.

For example, if I didn't know how the `fork(2)` system call worked, I would type

`man fork` into my terminal.

This would bring up the manual for the `fork(2)` system call.




:nerd: Navigating through a man page once it is open can be weird if you're not

familiar with these types of applications.

To scroll up and down, you simply use the **up arrow key** and **down arrow key**

or **j** and **k**, respectively.

To exit the page, simply type **q**.

That having been said, long `man` pages may look like a wall of text.

So it's useful to be able to search through a page.

This can be done by typing the **/** key, followed by your search phrase,

and then hitting **enter**.

Note that man pages are displayed with a program known as `less`.

For more information about navigating the `man` pages with `less`,

run `man less` in your terminal.




Now, you may have noticed the `2` in `fork(2)`.

This indicates the section in which the `man` page for `fork(2)` resides.

Here is a list of the `man` page sections and what they are for.




| Section | Contents |

| ----------------:|:--------------------------------------- |

| 1 | User Commands (Programs) |

| 2 | System Calls |

| 3 | C Library Functions |

| 4 | Devices and Special Files |

| 5 | File Formats and Conventions |

| 6 | Games et. al |

| 7 | Miscellanea |

| 8 | System Administration Tools and Daemons |




From the table above, we can see that `fork(2)` belongs to the system call section

of the `man` pages.

This is important because there are functions like `printf` which have multiple

entries in different sections of the `man` pages.

If you type `man printf` into your terminal, the `man` program will start looking

for that name starting from section 1.

If it can't find it, it'll go to section 2, then section 3 and so on.

However, there is actually a Bash user command called `printf`, so instead of getting

the `man` page for the `printf(3)` function which is located in `stdio.h`,

we get the `man` page for the Bash user command `printf(1)`.

If you specifically wanted the function from section 3 of the `man` pages,

you would enter `man 3 printf` into your terminal.




:scream: Remember this: **`man` pages are your bread and butter**.

Without them, you will have a very difficult time with this assignment.




## Getting Started




Fetch and merge the base code for `hw4` as described in `hw0`.

You can find it at this link: https://gitlab02.cs.stonybrook.edu/cse320/hw4




**NOTE:** For this assignment, you need to run the following command in order

for your Makefile to work:




```sh

$ sudo apt-get install libncurses5-dev

```




The `sudo` password for your VM is `cse320` unless you changed it.




Here is the structure of the base code:

<pre

hw4

├── include

│   ├── ecran.h

│   ├── session.h

│   └── vscreen.h

├── Makefile

└── src

├── ecran.c

├── mainloop.c

├── session.c

└── vscreen.c

</pre




If you run `make`, the code should compile correctly, resulting in an

executable `bin/ecran`. If you then run this program, what should happen

is that the terminal contents are cleared and you will see an error

message at the top line, advising you that you need to fill in some code

before the program will function. Exit `ecran` by typing `CTRL-a` followed

by `q`.




* To get the program to function, you first have to fill in a few lines

of code that have been left out of the `session_init()` function in `session.c`.

Find the place in this function where the error message you saw is generated.

At this point, you need to arrange for the standard input (file descriptor 0)

and the standard output (file descriptor 1) of the newly forked process to be

suitably set.




**HINT:** the standard error (file descriptor 2) was already

set up a few lines earlier and it was what was used to output the error message.

You need to "dup" this file descriptor to point the standard input and standard

output to the same place.




You also need to fill in a call to the proper `exec()` variant in order to execute

the program at `path` with the specified `argv` argument vector.




Once you have successfully filled in the missing lines of code, what should

happen when you run the program is that a prompt from the shell will appear

at the top line of the screen instead of the error message.

At this point, you are actually talking to a shell that is the session leader

for a virtual terminal session.

You should be able to run commands with this shell, essentially as if

it were a normal terminal session. However, you will probably notice that

the display is not as featureful as a normal terminal window would be.

For example, if you try to run `man sh` you will receive a message

`WARNING: terminal is not fully functional` and the output will be

peppered with funny sequences that look like `[m`.

This is because the virtual screen emulated by the base version of `ecran`

is a "dumb" terminal that does not support the escape sequences for

manipulating the screen contents that a normal terminal window supports.

If you get the basic terminal multiplexing functionality to work,

you will have an opportunity to earn extra credit points by improving

the terminal emulation.




The normal behavior of `ecran` is to send characters that you type to

the virtual terminal session and to take output coming from the virtual

terminal session and display it in the real terminal.

To exit from `ecran`, it is necessary to cause it to take something that

you type and **not** send it to the virtual terminal session but rather

interpret as a command. `Ecran` recognizes the special character

CTRL-a as an escape to command mode from normal processing.

If you type CTRL-a, followed by, say, `x`, you should see the screen flash.

This indicates that the command character `x` is not currently understood

by `ecran`. However, if you type CTRL-a followed by `q`, then

`ecran` should exit and restore the contents of your screen to what they

were before the program was started.




The basecode that we have provided to you implements some features essential

to managing virtual terminal sessions, but in its current form it can only

manage a single session. Your objective is to improve the program so that

it can seamlessly handle up to 10 virtual terminal sessions, with commands

for creating sessions, switching from one session to another, terminating

sessions, and so on.




## Add Debugging Support




To manipulate the terminal window, the `ecran` program uses a library

called `ncurses`. This library has evolved over many years from the original

`curses` library that was part of Unix System V. It is currently a part of

essentially every Linux and Unix distribution, and it is used to support

the behavior of terminal-based programs like `less` and `top`.

Your Linux Mint system is no exception, however that system does not assume

that you are going to be doing program development with the `ncurses` library.

That is the reason why you had to run the `apt-get install` command

indicated above.




When the `ncurses` library is initialized, it co-opts the normal terminal

behavior. This can make debugging awkward.

In order to use `gdb` to debug the program, you have to start `gdb` in a

separate terminal window and then attach to the process to be debugged.

Here is how you would do this: First, start `bin/ecran` in one terminal

window (which it will take over with `ncurses`). Then, in another terminal

window, run `ps -a` to find out the process ID of the process running

`bin/ecran`. Start `gdb` via `gdb bin/ecran` as usual. Once `gdb` is running,

issue the command `attach nnn`, where `nnn` is the process ID of the process

you want to debug. If all goes well, `gdb` should attach to the process,

the process should be stopped, and you will be able to browse the stack,

variables, etc. It might be that when you try to attach you will get

a permission failure. This is because restrictions are placed on which

processes can attach to which other processes for debugging, to avoid various

security holes. If this occurs, then on your VM, you should be able to work

around it by starting `gdb` with `sudo`.




Aside from using `gdb`, it would be usual to debug your program by using

the macros in `debug.h` to generate debugging printout. Once again, this

is made inconvenient by the fact that the entire terminal window has been

taken over by `ncurses`. To work around this, you are to implement an option

to redirect `stderr` output to a file, instead of to the terminal.




* Modify `ecran` so that if it is started with an option

`-o filename`, then it should arrange to redirect the standard error output to

the file whose name is `filename`. This file should be created if it does

not already exist, and truncated to 0 length if it does.

To implement this redirection, you will need to use the `open()`

and `dup2()` system calls. Read the man pages for these and refer to the

textbook as well. If you implement this feature correctly, then you will

be able to monitor the debugging output as `ecran` runs using the command

`less filename` (executed in a separate terminal window).

Once `less` is started, if you type `F` it will monitor the contents

of the file and show you immediately each time additional output is appended.




## Improve Terminal Emulation and Add Status Line




Before going on to the harder stuff, make the following easy improvements

to the terminal emulation implemented by virtual screens:




* The only control characters for which support is provided in the base code

are carriage return (`\r`, ASCII code 13) and line feed (`\n`, ASCII code 10)

Add support for the `BEL` character (`\a`, ASCII code 7). If this character

is output, the screen should flash. You can use the `flash()` function

provided by `ncurses` to do this.




:nerd: Documentation for the `ncurses` API can be found online

[here](https://invisible-island.net/ncurses/man/ncurses.3x.html).




* The base code terminal emulation does not implement scrolling.

Normal terminal behavior would be that if a line feed is output when the

cursor is at the bottom line of the screen, then all the lines are scrolled

up by one, the top line disappears, and a blank line is inserted as the

new bottom line. Implement that behavior.




Also, it will be useful to have a "status line", separate from the display

of the foreground virtual screen, in which messages for the user can be displayed.




* Arrange for the bottom line of the terminal window (the actual window,

not the virtual screens) to be reserved as a status line.

This can be done by using the `ncurses` function `newwin()` to create

two "windows": one that covers all but the last line of the screen

and the other that covers only the last line of the screen.

Set the `main_screen` variable in `ecran` to be a pointer to the big

window and create another variable to refer to the status window.

Implement a function `void set_status(char *status)` to set the contents

of the status line.




## Error Handling Code Review




* The base code supplied to you does not do a good job of checking for

error returns from library functions and system calls.

Review the entire base code (you may skip the code in `mainloop.c`)

to ensure that error returns from all functions are checked and handled

gracefully (without crashing). For some kinds of errors, the appropriate

response will be to terminate the program with status `EXIT_FAILURE`.

Instead of exiting abruptly, possibly leaving "leaked" session processes

and the terminal in an indeterminate state, an attempt should be made to

kill existing sessions and to cleanly shut down `curses` processing

before exiting. For other kinds of errors, displaying a message in

the status line would be appropriate.




## Session Management




Now you are ready to proceed to the "meat" of the assignment:

extending `ecran` to handle multiple terminal sessions.

Specifically, you are to do the following:




* Implement a command `CTRL-a n` (analogous to the already-implemented

`CTRL-a q`) to create a new virtual terminal session.

When you enter this command, `ecran` should start a new session,

fork a new instance of the default shell, make the new session the foreground

session, and cause the contents of the terminal window to correspond to

the contents of the new virtual terminal.

The original session does not disappear; it just goes temporarily into

the background, awaiting recall at a later time.




* Implement commands `CTRL-a 0`, `CTRL-a 1`, ..., `CTRL-a 9` for switching

between virtual terminal sessions. Your `ecran` program should be able to

manage up to 10 virtual terminal sessions, with session ID's 0 to 9.

The initial session should have session ID 0, and when you create an

additional session it should be assigned the lowest unused session ID.

Entering the command `CTRL-a 0` should switch the original session back

to the foreground and display the current contents of its virtual screen

in the terminal window. Entering the command `CTRL-a 1` should switch back

again to the newer session. If you enter, say, `CTRL-a 5` and there is

currently no session with session ID 5, then no change should occur and the

screen should flash (use `ncurses` function `flash()` for this).




* Now that you can create new virtual terminal sessions, you also have to

handle their termination. For this, you should install a handler for

the `SIGCHLD` signal. Upon receipt of `SIGCHLD` you should arrange for

any terminated session leaders to be reaped and the session deallocated.

Be sure to pay close attention to what the textbook and lectures have to

say about what it is and is not safe to do in a signal handler.

If it is the foreground session that has terminated, then another session

should be set as the new foreground session. If there are no more sessions,

then `ecran` should terminate cleanly.




* Implement commands `CTRL-a k 0`, `CTRL-a k 1`, ..., `CTRL-a k 9` for

killing (forcibly terminating) terminal sessions. That is, when the

user types `CTRL-a k 5` the virtual terminal session with session ID 5

is terminated by sending `SIGKILL` to its process group.

If the user types `CTRL-a k` followed by a digit that does not correspond

to an existing session, or if the user types `CTRL-a k` followed by a

non-digit character, the screen should flash and nothing should be killed.




* Extend the implementation of the `CTRL-a q` (quit) command given in the

base code so that it behaves as follows:

First, terminate all sessions by sending `SIGKILL` to their respective

process group. After cleaning up **all** resources (reaping all

session leader processes, closing ptys, freeing `VSCREEN` and `SESSION`

structures and their contents, and shutting down `curses` processing

to restore the original screen contents), `ecran` should exit with

status `EXIT_SUCCESS`.

(Note that the version of this command that is implemented in the base code

already shuts down `curses` and exits the program, but it does not terminate

sessions or clean up properly.)




## Command Execution




* Extend the behavior of `ecran` so that any command-line arguments given

following the initial `-o filename` option are treated as the name of

a command to be executed and its arguments. So, for example, if you

run `bin/ecran ps a` the command `ps a` will be executed in the initial

virtual session, rather than the default shell.

Likewise, if you run `bin/ecran -o xxx ps a`, the command `ps a` will

be executed in the initial virtual session and the standard error output

of `ecran` (**not** that of `ps`) will be redirected to the file `xxx`.

 

Note that it should not be necessary to use a complete pathname (e.g. `/bin/ps`)

to specify the program to be executed; the value of the `PATH` environment

variable should be used to determine the search path for locating the

executable.




## Extra Credit




Some options for extra credit are available for this assignment.

Before attempting extra credit, make sure that the basic requirements

(discussed above) work perfectly.

You will have to tell us what extra credit to look for in your submission.

Do this by creating (and committing) a file `EXTRA_CREDIT` in your `hw4`

directory. This file should simply consist of a sequence of keywords,

each one on a separate line, corresponding to the extra credit items that

you attempted and wish to have evaluated. The keyword for each of the

extra credit items is given below, along with the number of points that

item is worth. One hundred points of extra credit is comparable to the

value of a single normal homework assignment (HW1 - HW5).

To receive extra credit for an item, it must work perfectly; there is

no partial credit given on extra credit items.




### ANSI Terminal Emulation (Keyword: `ANSI_EMULATION`, Points: 20)




Find out the control characters and escape sequences that are supported

by an ANSI terminal (e.g. see [here](http://ascii-table.com/ansi-escape-sequences.php)).

These control characters and escape sequences are sent to a terminal to

cause it to do things such as positioning the cursor and clearing portions

of the screen.

Besides carriage return and line feed, which are already implemented,

other important control characters are backspace (`\b`, ASCII code 8),

which moves the cursor back one column, horizontal tab (`\t`, ASCII code 9),

which moves the cursor forward by at least one column to the next tab stop

(tab stops are every eight columns, starting with the ninth column),

and form feed (`\f`, ASCII code 12), which clears the screen.

ANSI escape sequences all start with the two character sequence

`ESC [`, where `ESC` has ASCII code 27.




Extend the terminal emulation capabilities supported by the `ecran`

virtual screen to handle these control characters and escape sequences.

(You may omit those having to do with "graphics mode", "set mode", "reset mode",

and "set keyboard strings".)

Change the code in `session.c` so that it sets the `TERM` environment variable

for a new session to `ansi`, as opposed to the original `dumb`.

If you do this correctly, programs such as `less` and `top` that use

cursor-positioning capabilities should behave correctly when run in an

`ecran` virtual screen.







### Split Screen (Keyword: `SPLIT_SCREEN`, Points: 20)




Implement a "split screen" command `CTRL-a s` that splits the main screen

in half vertically, thereby creating side-by-side windows that can be

used independently to view any of the `ecran` virtual sessions.

For simplicity, the `CTRL-a s` command should "toggle" the split screen function:

the first time it is typed, the screen should be split and the current

session displayed in both halves. If `CTRL-a s` is typed again when

in split screen mode, the screen should revert to its normal behavior,

displaying as the foreground session what was previously the foreground

session in the left half-screen.




### Enhanced Status (Keyword: `ENHANCED_STATUS`, Points: 10)




Arrange for the current time of day and the number of existing virtual sessions

to be always shown at the right-hand side of the status line.

The time of day should be in HH:MM:SS format, and the information should update once

per second. One way to do this is to use the `alarm()` system call and a

`SIGALRM` handler. Another way to do it would be to modify the parameters

to the `select()` call in `mainloop()`, but you should not attempt to do it that

way unless you are very keen to understand how `select()` works and you have some

time to spend on debugging.




### Help Screen (Keyword: `HELP_SCREEN`, Points: 10)




Implement a command `CTRL-A h` which when typed, switches to a virtual screen

that displays a summary of the commands `ecran` understands and the sessions

that are currently active. This screen should be displayed until the `ESC`

key is pressed, at which time the display switches back to the foreground

session.




## Hand-in instructions

As usual, make sure your homework compiles before submitting.

Test it carefully to be sure that doesn't crash or exhibit "flaky" behavior

due to race conditions.

Use `valgrind` to check for memory errors and leaks.

Besides `--leak-check=full`, also use the option `--track-fds=yes`

to check whether your program is leaking file descriptors because

they haven't been properly closed.




Submit your work using `git submit` as usual.

This homework's tag is: `hw4`.

More products