Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -135,16 +135,20 @@ UPROGS=\
$U/_rm\
$U/_sh\
$U/_stressfs\
$U/_sleep\
$U/_usertests\
$U/_grind\
$U/_wc\
$U/_zombie\
$U/_logstress\
$U/_forphan\
$U/_dorphan\
$U/_cat\
$U/_myshell\
$U/_smash\

fs.img: mkfs/mkfs README.md $(UPROGS)
mkfs/mkfs fs.img README.md $(UPROGS)
fs.img: mkfs/mkfs README.md $(UPROGS) user/test.txt
mkfs/mkfs fs.img README.md $(UPROGS) user/test.txt

-include kernel/*.d user/*.d

Expand Down
81 changes: 81 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,85 @@ Fall 2025 Edition

![FogOS](docs/fogos.gif)

## 📋 Overview
This project extends the FogOS/xv6 `cat` utility with several Linux-style options to demonstrate user-space programming, system-call–based file I/O, and command-line parsing. Implemented flags include `-n` (number all lines), `-b` (number nonempty lines), `-E` (show `$` at end of each line), and `-s` (squeeze consecutive blank lines). The work preserves original `cat` behavior while adding composable formatting features.

## ✨ Implemented Features
- **`-n`** — Number **all** output lines
- **`-b`** — Number **nonempty** output lines only
- **`-E`** — Display `$` at the **end of each line**
- **`-s`** — Squeeze multiple **consecutive blank lines** into a single blank line

*Options can be combined.*
Example:
```bash
cat -nEs file.txt
```

## 🛠 Build Instructions
From the **project root directory**, run:
```bash
make clean
make qemu
```

## ▶️ Run Instructions
Inside the FogOS shell:
```bash
cat [options] [file...]

If no file is provided, cat reads from standard input.

Multiple options can be combined and multiple files can be listed.
```

## 💡 Usage Examples
```bash
$ cat -n test.txt
1 hello
2 world

$ cat -bE test.txt
1 hello$
$
2 world$

$ echo -e "foo\nbar" | cat -nEs
1 foo$
2 bar$
```

## ✅ Testing

### Manual Tests
The following commands were verified inside QEMU:
```bash
cat -n test.txt # number all lines
cat -b test.txt # number nonempty lines only
cat -E test.txt # show $ at end of each line
cat -s test.txt # squeeze consecutive blank lines
cat -nEs test.txt # combine options
echo "abc" | cat -n # read from stdin
cat -n file1 file2 # multiple files

Edge Cases

Empty files

Files with only blank lines

Consecutive blank lines

Large files to confirm no performance regressions
```

## 📂 Source Files
- `user/cat.c` — Enhanced implementation of the `cat` command
- `Makefile` — Updated `UPROGS` list to include the rebuilt `cat`

---

## 🔖 Notes
- Code follows xv6/FogOS formatting standards (2-space indentation, minimal library usage).
- Performance is equivalent to the original `cat` (no regressions).
- This README provides all required documentation for building, running, and testing the software.
125 changes: 125 additions & 0 deletions docs/smash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Project 2: Smash Shell
**Author**: Yifan Wan

## About This Project
**Smash** (Super Minimal Awesome Shell) is a feature-rich command-line interface designed for the xv6 operating system (RISC-V). It serves as the primary interface between the user and the kernel, replacing the default `sh`.

Smash is designed to mimic the behavior of modern Unix shells like `bash` or `zsh`, supporting advanced features such as process pipelines, I/O redirection, background job execution, and command history management. Additionally, significant modifications were made to the xv6 kernel to support features like script execution (Shebang) and file appending.

## Features

### 1. Interactive Prompt
The shell displays a dynamic prompt containing useful context:
`[Status]-[Count]─[Directory]$`
* **Status**: The exit code of the previous command (0 for success, non-zero for failure).
* **Count**: The sequential number of the current command.
* **Directory**: The current working directory (e.g., `/home` or `/`).

### 2. Built-in Commands
Smash handles the following commands internally (without forking):
* `cd <path>`: Changes the current working directory.
* `exit`: Terminates the shell session.
* `history [-t]`: Displays the last 100 commands.
* **-t**: Shows the execution duration of each command in milliseconds.
* `!n`: Re-executes command number *n* from history.
* `!prefix`: Re-executes the last command starting with *prefix*.
* `!!`: Re-executes the immediate previous command.

### 3. I/O Redirection & Pipelines
Smash supports complex command chaining:
* `>`: Overwrite standard output to a file (e.g., `echo hello > file.txt`).
* `>>`: Append standard output to a file (e.g., `echo world >> file.txt`).
* `<`: Redirect standard input from a file (e.g., `cat < file.txt`).
* `|`: Pipe the output of one command to the input of another (e.g., `ls | grep txt | wc -l`).

### 4. Process Management
* **Background Jobs**: Ending a command with `&` runs it in the background, allowing the user to immediately enter new commands without waiting.
* **Path Execution**: Uses a custom `execvp` logic to find binaries. It searches in the following priority:
1. Absolute/Relative path (e.g., `./script.sh`).
2. Root directory (e.g., `/ls`).
3. Current directory.

### 5. Scripting Support
* **Batch Execution**: `smash script.sh` executes commands from a file.
* **Shebang**: Kernel support for `#!/smash` allows scripts to be executed directly (e.g., `./script.sh`).
* **Comments**: Lines starting with `#` are ignored.

---

## Kernel Modifications
To support the advanced features of Smash, several modifications were made to the xv6 kernel:

### 1. `sys_getcwd` (System Call)
* **File**: `kernel/sysfile.c`, `user/user.h`
* **Purpose**: Added a system call to retrieve the current working directory string from the process's `cwd` inode by traversing up to the root. This is required for the dynamic shell prompt.

### 2. `O_APPEND` Support
* **File**: `kernel/fcntl.h`, `kernel/sysfile.c`
* **Purpose**: Added the `O_APPEND` flag (0x004). Modified `sys_open` to detect this flag and set the file offset (`f->off`) to the file size (`ip->size`) immediately after opening, enabling the `>>` operator.

### 3. Shebang (`#!`) Support
* **File**: `kernel/exec.c`
* **Purpose**: Modified the `kexec` function. When loading a file, if the ELF magic number is missing, it checks the first two bytes for `#!`. If found, it parses the interpreter path (e.g., `/smash`) and recursively calls `kexec` to run the interpreter with the script as an argument.

---

## Implementation Details

### The Parsing Logic
The shell uses a custom tokenizer to split input by whitespace. It then parses the tokens in passes:
1. **Background Check**: Checks if the last token is `&`.
2. **Pipeline Split**: Splits the command into segments based on `|`.
3. **Execution Loop**: Iterates through segments, creating pipes `pipe()` and forking `fork()` for each command.
4. **Redirection**: Inside the child process, before execution, the arguments are scanned for `<`, `>`, `>>`. `close(0)` or `close(1)` are used followed by `open()` to replace file descriptors.

### History Implementation
History is stored in a global array of structs to prevent stack overflow. Each entry stores the command string and its execution duration. The duration is calculated using the `uptime()` system call (ticks converted to ms) before and after the wait loop.

---

## Testing
To compile and run the shell:

```bash
make clean
make qemu
```

Once inside xv6, start the shell:

```bash
$ smash
```

### Test Cases

**1. Redirection & Append:**
```bash
echo hello > test.txt
echo world >> test.txt
cat test.txt
# Output should be hello\nworld
```

**2. Pipes:**
```bash
ls | grep test
```

**3. Background Jobs:**
```bash
sleep 100 &
# Shell should immediately return prompt
```

**4. Scripting:**
```bash
# Create a file named test.sh:
#!/smash
echo "Running script"
# This is a comment
ls

# Run it:
./test.sh
```
Loading