Skip to main content Link Search Menu Expand Document (external link)

50.005 Computer System Engineering
Information Systems Technology and Design
Singapore University of Technology and Design
Natalie Agus (Summer 2024)

CSEShell Starter Code

CSEShell is a simple, custom shell for Unix-based systems, designed to provide an interface for executing system programs. This project includes a basic shell framework, a set of system programs (find, ld, ldr), and some test files.

It incorporates a simple prompt display mechanism and the ability to exit the shell. However, it lacks the typical continuous loop for reading and processing commands, as well as the process forking logic (fork) that is commonly used in shell implementations to execute commands in separate processes.

Usage Explanation

Directory Structure

The project is organized as follows:

  • bin/ - Contains compiled executables for system programs.
    • find - Program to find files.
    • ld - Program for listing the contents of the current directory.
    • ldr - Program for listing the contents of the current directory recursively.
  • cseshell - The main executable for the CSEShell.
  • files/ - Contains various test files used with the shell and system programs.
    • combined.txt, file1.txt, file2.txt, … - Test text files.
    • notes.pdf - A PDF file for testing.
    • ss.png - An image file.
  • makefile - Makefile for building the CSEShell and system programs.
  • source/ - Source code for the shell and system programs.
    • shell.c and shell.h - Source and header files for the shell.
    • system_programs/ - Source code and header for the system programs.

Building the Project

To build the CSEShell and system programs, run the following command in the root project directory:

make

This will compile the source code and place the executable files in the appropriate directories.

Running CSEShell

After building, you can start the shell by running:

./cseshell

From there, you can execute built-in commands and any of the included system programs (e.g., find, ld, ldr).

You can type exit to exit the shell:

As the starter code only contains basic shell framework, it lacks the typical continuous loop for reading an processing commaands. The shell will terminate each time you enter a command. To type another command, you need to run ./cseshell again.

Note that only system programs at [PROJECT_DIR]/bin is currently accessible by the shell, and hence only these three commands: find, ld, ldr are supported.

If you try to type any command that’s commonly available on your system’s shell, such as pwd, you will be met with this error message:

System Programs

  • find.c - Searches for files in a directory.
  • ld.c - List the contents of a directory.
  • ldr.c - List the contents of a directory recursively.

Each program can be executed from the CSEShell once it is running.

Files Directory

The files/ directory contains various text, PDF, and image files for testing the functionality of the CSEShell and its system programs.

Makefile

The Makefile contains rules for compiling the shell and system programs. You can clean the build by running:

make clean

Source Directory

Contains all the necessary source code for the shell and system programs. It is divided into the shell implementation (shell.c, shell.h) and system programs (system_programs/).

Detailed Explanation of shell.c

shell.c contains a basic implementation of a shell that reads commands from the user, parses them, and executes them as processes. It consists of three main functions (read_command, type_prompt, and main). Below is an expanded documentation for each part of the code, including both the existing comments and additional explanations where needed.

Header and Global Definitions

#include "shell.h"

Purpose

Includes the header file shell.h that presumably contains necessary constants (like MAX_LINE and MAX_ARGS) and function declarations related to the shell implementation.

Function: read_command

void read_command(char **cmd)

Purpose

Reads a single command from the standard input (stdin), parses it into arguments, and stores the result in the provided cmd array.

Parameters

char **cmd - A pointer to an array of strings, where the command and its arguments will be stored.

Functionality:

  • Reads characters from stdin until a newline ('\n') or the maximum line length (MAX_LINE) is reached.
  • If the command is too long, it prints an error message and exits.
  • Parses the read line into words using strtok and stores each word in a dynamically allocated array.
  • Copies the parsed words into the provided cmd array, ensuring it’s NULL-terminated.

Function: type_prompt

void type_prompt()

Purpose

Displays the shell prompt to the user.

Functionality:

  • If it’s the first time the function is called, clears the screen.
  • Prints the prompt ($$ ) to the standard output (stdout).

Function: main

int main(void)

Purpose

Implements the main functionality of the shell. It displays a prompt, reads and parses a command, and then executes it.

Error Handling and System Calls

Proper error checking is performed for critical operations like reading from stdin and executing commands using execv system calls. It uses exit(1) to terminate the program upon encountering fatal errors.


    // in read_command(char** cmd)
    // If the command exceeds the maximum length, print an error and exit
    if (count >= MAX_LINE)
    {
      printf("Command is too long, unable to process\n");
      exit(1);
    }
    ...

    // in main()
    execv(full_path, cmd);

    // If execv returns, command execution has failed
    printf("Command %s not found\n", cmd[0]);
    exit(0);

Memory Management

Dynamically allocates memory for each argument parsed in read_command using strdup. It’s important to free this memory at the end of main to prevent memory leaks.

  // Free the allocated memory for the command arguments before exiting
  for (int i = 0; cmd[i] != NULL; i++)
  {
    free(cmd[i]);
  }
  memset(cwd, '\0', sizeof(cwd)); // clear the cwd array

Portability

Uses preprocessor directives to clear the screen in a way that is compatible with both Windows (cls) and Unix-like systems (clear).

#ifdef _WIN32
    system("cls"); // Windows command to clear screen
#else
    system("clear"); // UNIX/Linux command to clear screen
#endif
    first_time = 0;