Project 4: Writing Files + Improved Shell
Due: Tuesday, Nov 27, 11:59:59 p.m.
Table of Contents:
Objective
In this project you will implement functions for deleting and writing files, and add several new commands to your shell. At the end of the project, you will have a fully functional single-process operating system about as powerful as CP/M, an early PC operating system. If that doesn't get your juices flowing, I don't know what will.
Background
Like reading files, writing files requires that you understand the file system and how it keeps track of the names of the files on the disk and their locations. As such, there is no new background for this project. However, you may want to review the material on the Disk Directory and, especially the Disk Map given in the Background section of Project 3.
Getting Started
Go to the GitHub Assignment to
set up your repository. Start your VM, open a terminal, and clone your GitHub repository
When finished, you should have a directory
named project4_username
containing the files.
Project 4 - Refactored
Since kernel.c
was getting pretty long, I refactored
the code into several modules and updated the Makefile
accordingly. While I prefer you would use your code for a better
sense of ownership, I know that refactoring your code may be a more
onerous task that you would like at this point. If you use my code,
make sure you understand the functions I provide.
The "default" filesys.c
uses struct
s for the disk
directory information. If you don't want to use structs,
copy filesys.c
and kernellib.c
from
the nostructs
directory.
Your [new] code should be put into the appropriate files.
Step 1: Writing a Disk Sector
The functionality for reading files was based on being able to read
a sector. Similarly, the functionality for writing files will be
based on being able to write a sector. Create a new function named
writeSector
in your kernel with the prototype:
int writeSector(char *buffer, int sector);
Writing sectors can be accomplished using the same BIOS call as
reading sectors. The only difference is that AH
should
equal 3 instead of 2. Thus, you should be able to copy your
readSector
function and modify it fairly easily to write
the data in buffer to the indicated sector. You can refer to the
Project 2 assignment
for the details of the BIOS call used in the
readSector
function.
To test your writeSector
function, you can have
your main
function write some known values into the first
sector (0) or the last sector (2879) on the disk. You can then use
the hexdump
program to read the floppya.img
disk image. If writeSector
is working correctly the
values that were written should appear at the beginning or the end of
the disk image.
Since we do not want user programs writing arbitrary sectors on the
disk, we will not provide access to writeSector
through
the system call interface.
Step 2: Deleting a File
Deleting a file requires two steps:
- All of the sectors allocated to the file must be marked as
free (i.e., set to
0x00
) in the Disk Map. - The first character of the filename in the Disk Directory must
be set to
0x00
.
These two steps do not actually erase the file (or even its full filename) from the disk. Rather, it just makes the directory entry and sectors used by the file available to be used for new files. The advantages of deleting files in this way are that it is fast and it also becomes possible to undelete a file (at least until its directory entry or sectors are reused). Most modern operating systems use a deletion process similar to this one.
Add a deleteFile
function to your kernel with the
following signature:
int deleteFile(char *fname);
This function should delete the named file using the two-step process described above. If the file is found and deleted, this function should return 1. If the file to be deleted cannot be found on the disk, this function should return -1. After the Disk Map and Disk Directory are modified, they will need to be written back to the disk to save the changes.
Test your deleteFile
function by having your main
function delete a file (or files). Then, open
the floppya.img
disk image and examine the Disk Map and
Disk Directory to ensure that they have been modified to reflect the
deleted files.
System Call
Add and test a system call for deleting a file by modifying your
handleInterrupt21
function so that it provides the
following service:
deleteFile: delete a file from the disk. AX: 0x07 BX: The name of the file to be deleted. CX: Unused DX: Unused Return: 1 if the file is successfully deleted or -1 if the file cannot be found.
Step 3. Writing a File
Writing a file requires finding an empty entry in the Disk Directory and finding a free sector in the Disk Map for each sector making up the file. The data for each sector is written into a free sector on the disk and the sector numbers that are used are entered into the Disk Directory entry for the file. The modified Disk Directory and Disk Map must then be written back to the disk to save the changes.
Add a writeFile
function to your kernel with the
signature:
int writeFile(char *fname, char *buffer, int sectors);
This function should write sectors * 512 bytes of data from the
buffer into a file with the name indicated by fname
,
where sectors is the number of sectors to be written. If the file
indicated by fname
already exists, the new file
overwrites it. The maximum number of sectors that may be written is
26. If sectors is larger than 26, then only the first 26 sectors
should be written. If the file is successfully written, this function
returns the number of sectors that were written. If there is no Disk
Directory entry available for the new file, this function should
return -1 and the file is not written. If the Disk Map contains fewer
than sectors
free sectors, this function should write as
many sectors as possible and return -2.
If you didn't decompose your kernel's functions for the last project, make sure you decompose for this project. What are good, reusable pieces of code that should be put into functions?
One way to test your writeFile
function is to use your
readFile
function to read a file into a buffer then write
it to a new file. Once you have written the new file you can then use
readFile
again to read it in and print it out. If the
contents are the same as the original file, that is an indication (but
not a guarantee) that your writeFile
function is working
correctly.
What else should you test?
System Call
Add and test a system call for writing a file by modifying your
handleInterrupt21
function so that it provides the following service:
writeFile: write a file to the disk. AX: 0x08 BX: The name of the file to be written. CX: The address of a buffer containing the data for the file. DX: The number of sectors to be written. Return: The number of sectors written or -1 if there is no Disk Directory entry available for the file or -2 if there are insufficient free sectors to hold the file.
Step 4. User Library Improvements
Add functions to the user library for deleting a file and writing a file to disk.
Step 5. Shell Improvements
Add new functionality to your shell, as described below.
Shell Command: delete <file>
Extend your shell so that it recognizes the command: delete <file>. This command should remove the specified file from the disk. If the file does not exist on the disk, the shell should print “File not found.”
Shell Command: copy <src> <dest>
Extend your shell so that it recognizes the command: copy <src> <dest>. This command should make a copy of the file <src> as a new file <dest>. If the destination file already exists, it should be overwritten. If the source file cannot be found, then the shell should print “File not found.” If the destination file cannot be created because the directory is full, the shell should print "Disk directory is full." If the disk becomes full before the entire source file is copied, the shell should print "Disk is full."
Shell Command: dir
Extend your shell so that it recognizes the
command: dir. This command should display a list of the
files currently stored on the disk. Since we do not want to allow
general direct access to sectors, you need to add a specialized function
in
userlib.c
to access the disk directory contents. There are
various ways you could handle this. You may need to update
kernel.c
accordingly.
If you have the printInt
function, you might also have
the dir
command display the size, in sectors, of each
file.
Step 6: Finalizing your Submission
(If you choose to complete the bonus features, come back to this after completing them.)
Running your kernel should look pretty much like it did at the end of Step 5: a running shell. I didn't explicitly talk as much about testing in this project. Since this is the fourth OS project, I hope you know how to test it. Review your code and make sure you have done appropriate error handling and documenting of your code.
Bonus Features
I will provide only limited help on the bonuses.
If you complete a bonus, it should be documented in comments so that I can find it easily.
- Create a simple line-based text editor that can be executed as
a user program, e.g.,
nano
. When executed your text editor should prompt the user to enter a filename into which the entered text will be stored. The editor should then read lines of text from the user, storing them into a buffer. The editor should not allow the user to enter more text than will fit in a file on the system. When the user types CTRL-D (ASCII0x04
) followed by the return key, the editor saves the file and terminates. If the file specified by the user already exists, it is overwritten with the new text. Have yourMakefile
compile your editor and add it to your disk image. - Most modern operating systems make it possible to have
multiple names for the same file. In Windows these are called
shortcuts, in Mac OS they are called aliases, and in Unix they are
called symbolic links. You can learn more about symbolic links in
Unix by typing the command man ln in the terminal window.
Add a system call to your kernel that creates a symbolic link to a file. Your system call should be implemented by modifying your handleInterrupt21 function so that it provides the following service:
linkToFile: create a symbolic link to a file AX: 0xA0 BX: The name of the file to link to. CX: The name of the link. DX: Unused Return: 1 if the link is successfully created or -1 if the file to link to does not exist or -2 if the link cannot be created because the Disk Directory is full.
The name of the symbolic link should appear in the Disk Directory. To indicate that the Disk Directory entry is a symbolic link and not an actual file, the first bytes of the entry that indicate the file’s sectors should be set to
0xFF
. The next six bytes can then be used to indicate the filename of the file to which the symbolic link points.Create a user library function for creating symbolic links and add a
symlnk <src> <dest>
command to your shell.When completed, all of your OS functionality should work equally well on real files and symbolic links. For everything except deleting a file, commands operating on symbolic links should behave exactly as if they were operating on the actual file (e.g., type, copy). When a symbolic link is deleted, only the link is deleted; the original file is unchanged.
Note that if the file pointed to by a symbolic link is deleted the link should remain. An attempt to type or copy such a symbolic link should report “File not found.” This also implies that if a new file is created with the same name as pointed to by the link, then the link will be a link to the new file!
- Implement command history in your shell. In most Unix shells, the up and down arrow keys will scroll back and forth through a list of some number of previously entered commands. The previous commands are displayed at the command prompt, and the user can press enter to execute that command.
- Implement scripting in your shell. The execute command should be able to execute either a standard executable program or a shell script. Shell scripts should be standard text files that begin with the character sequence "#!". In this case, the shebang is essentially a magic number for identifying shell scripts. If the file that is executed is a shell script, the shell should read each line from the file and attempt to execute it as if it were typed at the command line. Note that with this functionality you can now use your text editor to create a program (a shell script) that you can then run!
Submission
GitHub Classroom will make a snapshot of your repository at the deadline. It can only see the code that you pushed to the repository. Your repository should contain your source code and the updated Makefile.
I should be able to clone/pull your code and
run build.sh
(or make
) and
then run.sh
to see your kernel running. You can (and
should) follow the same process that I will follow to verify that your
code is all there and correct.
Assessment
You will be graded on:
- (90) Required Functionality
- Kernel Improvements (55)
- User Library Improvements (10)
- Shell Improvements (20)
- Makefile (5)
- (10) Code style: proper comments/documentation, format, function decomposition, code organization
Acknowledgement
This assignment as well as the accompanying files and source code have been adopted with minor adaptations from those developed by Michael Black at American University. His paper "Build an operating system from scratch: a project for an introductory operating systems course" can be found in the ACM Digital Library.