Skip to content

hmrguez/Project-Nautilus

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

3 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Nautilus Shell

Nautilus is a Unix shell written in C that allows users to interact with the operating system through commands. It includes several features commonly found in Unix shells, such as input/output redirection and multipiping. This project is part of our Operating Systems course in the 3rd year of Computer Science.

Index

Contributors

  • Hector Miguel Rodriguez Sosa - Group C311\
  • Sebastian Suarez Gomez - Group C311

Features

  • Input redirection from a file instead of the keyboard (<)\
  • Output redirection to a file instead of the console (>)\
  • Output redirection to a file but appending instead of overwriting (>>)\
  • Multipiping, which allows chaining multiple commands by using the output of one command as the input of the next (|)\
  • cd: lets the user change the working directory\
  • help: provides information about the shell's functionalities\
  • exit: exits the shell\
  • history: displays the last 10 commands used on screen\
  • again: given an index 0--9, re-executes the command from the history at that index\
  • Ctrl+C: kills the current process using signals

Usage

To use Nautilus, simply clone the repository, compile the main.c file, and run the generated executable from the root folder. An example using GCC is:

gcc -o main main.c && ./main

The shell will display a prompt like:

nautilus $ 

You should then type your commands there. For more detailed information, type help.

Technical Details

Basic

Command reading is handled in the method ntl_loop, which prints the shell prompt as long as the entered command is empty or when a command finishes. It then passes execution to the ntl_execute method (L532), whose role is to handle some edge cases and, above all, to split the input line into commands and separators. Then the ntl_parsing method (L391) iterates over the commands and separators and executes each command depending on which separators surround it.

Any command can be executed using the execvp(...) function. This can be seen in the ntl_launch function (L337), which forks the main command (the shell) and passes it to execvp. If it returns an error, the same error is propagated back.

The other arguments of ntl_launch are used for piping and input/output redirection. There are 6 possible options:
0. The command is plain (normal input and output).\

  1. Output goes to a file (>).\
  2. Input comes from a file (<).\
  3. Output is appended to a file (>>).\
  4. Both input and output use files (e.g., command1 < file1 > file2).\
  5. Same as 5 but with output append (command1 < file1 >> file2).

Piping is explained in help multi-pipe.

Ctrl+C

Our shell allows sending a Ctrl+C while a command is running.

To implement this, we first created a signal handler (sig_handler) that captures any incoming SIGINT. It then passes it to the parent process (the shell), which warns the child by sending the same SIGINT. If the child decides to ignore it for any reason, a variable is set marking that a SIGINT has already been sent. If another SIGINT is sent, then the process is forcefully killed with a SIGTERM.

The signal handler is assigned in the method at (L24), which also ensures the process is running in the foreground and sets the shell as the parent of all processes. The handler itself is set at (L32) and (L33), while the handler function is defined at (L56).

There is a small bug: when a process is killed by SIGINT, the shell sometimes displays the prompt twice. Another related issue is that when builtin commands are killed, multiple prompts may be displayed.

History

The builtins history and again were implemented. The history builtin (L184) reads the contents of a pre-created file, which stores the last 10 commands.

Commands are stored right after a line is read using the append_to_history function (L215). Since only the last 10 commands can be stored, this function maintains the file as a circular queue: if there are already 10 commands when adding a new one, the oldest one is removed and the new one is added.

The again builtin (L289) allows the user to type again {index}, which retrieves the command from history at that index and passes it to ntl_execute(). That command is also saved in history again.

Multipipe

For piping, the pipe() function is not used. Instead, buffer files are used to store the output of one command and provide it as input to the next.

In practice, this works just like normal piping. In simple terms, when executing command1 | command2, the shell actually interprets it as command1 > buffer_file1 ; command2 < buffer_file1. The buffer file is always deleted afterwards.

This approach makes the implementation easier, since it reuses the same logic already implemented for < and >. It's similar to using pipe(), but instead of a file descriptor, it uses an ephemeral real file inside the folder.

Piping starts at (L458). When the next operator is detected to be a pipe, it enters "pipe mode," where three cases can occur:\

  1. The first command is executed with ntl_launch **(L337)** using option 1 (write to buffer file).\
  2. The last command is executed with option 2 (read from buffer file).\
  3. Middle commands are trickier: as with pipe(), reading and writing from the same file causes errors. So two buffer files are alternated. If one command reads from buffer1, the next command writes to buffer2. On the next iteration, they are swapped again.

About

A unix shell written in C. It's a C-shell. Get it πŸ˜‚πŸ˜‚?

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors