NunyaOS' Filesystem
Basics
Right now it’s a bit of a mess but the main ideas follow. The general idea is that we allow user processes to access the filesystem through the system calls open, close, read, and write (although we have no way to write yet). Each of these calls from user land goes through a security check which ensures that the process requesting the file is allowed to access it. The system call must use the absolute path for the time being.
A filesystem path should be put together as follows: /0/bin/userproc.nun, where
- /
- this is the root of file systems on Nunya
- 0
- this is the ata unit that you are accessing, which can be 0, 1, 2, or 3
- /
- this is the root of the ata unit you are on
- bin/userproc.nun
- this is the rest of the path as you would see it when you are only on that particular media
How does the filesystem ensure security?
The way that security is checked is by running through a process’s linked list of file system allowances, which describe whether or not each process is able to see a certain part of the file system and what is below it. In this way, you are able to as a parent process dictate any possible subset of your allowances to your child processes, and this is done through capabilities system calls. These capabilities system calls are in code review (https://github.com/NunyaOS/Nunya/pull/72) and will be explained later in the document.
How are files opened?
Once the call passes the security check, it should look up what type of media is attached to the ata unit (between the first two slashes of the path) by looking at a table we’ve yet to create which the low level ata code must be made to fill in. Essentially, someone needs to find out how you can identify the type of filesystem (FAT32, iso9660, etc.) when probing for ata units, keep that in a table, and then read the table when a system call comes to figure out which driver functions to call. Unfortunately, the only type of file system we considered is an .iso filesystem (because we only had so much time to work with all of the different file system types). As a result, when something needs to be accessed from the filesystem ata unit 3 is assumed to be an iso9660 filesystem, and the run command currently just uses iso_open as opposed to a system open.
On open, the process will get a file descriptor which it can make read, write, and close calls on. In the process struct, there is an array of fs_agnostic files, which describe the ata type (what kind of filesystem it is in, FAT32, ISO-9660, what have you) and holds a void pointer to that lower level representation of the file (such as a struct iso_file). By switching on the ata type you can write code (mainly switch statements) that knows how to cast the pointer to the lower level struct and call the right functions to manipulate it when reading or writing.
How are files closed?
On close()
, the processes fs_agnostic file for that descriptor is set to have is_open as true, so that the file descriptor can be used again. It also closes and frees the underlying structure (struct iso_file). Further attempts to access a file using that file descriptor will fail (unless of course it is assigned again by another open call).
How can I set the filesystem permissions of a process?
Now we’ll talk a little bit about the hierarchical containment that the file system offers (is about to offer, after PR 72 is merged). The idea behind these calls is that when a user process (process 1) creates a permissions_capabilities struct (which is below kernel level), that struct has an exact copy of process 1’s fs allowances. The system calls which add and remove allowances are written such that the access provided by this list of allowances is monotonically decreasing. Although having a call to add capabilities might seem counterintuitive to this, the idea is that you can only add allowances which duplicate an access that you already have. Removing an allowance removes it for good, so the writer of a user process which call run()
must be careful in the ordering of his/her calls to restrict file system access for future children. For example, if the parent has access to /3/ and everything below it, and /3 has subdirectories /3/a, /3/b, and /3/c, and it wants to grant its child access to only /3/a and what is below it, it would do the following:
int ident = create_permissions_capability();
permissions_capability_add_allowance(ident, “/3/a”, 1);
permissisions_capability_remove_allowance(ident, “/3”);
run(“PATH_TO_BINARY”, ident);
You can see that the call to permissions_capability add_allowance merely duplicates allowances, because the current list is [/3/a and below] -> [/3 and below], so you see that adding an allowance is not actually expanding permissions but allowing for them to be later restricted by a call to remove an allowance.
Of course if a mistake is made, in the ordering, another call to create_permissions_capability()
will give a fresh duplicate of the current process’s file system allowances.
When run()
is called, and given an identifier to a permissions_capbilities struct, the list of fs_allowances which may or may not have been pared down from the parent’s allowances, is copied to the new process’s permissions list of fs allowances (It is copied to struct process_permissions->fs_allowances). Because the file system security checks were in place before the concept of permissions was conceived, all of the checks occur on the process’s fs_allowances_list, not on the process’s process_permissions->fs_allowances. Therefore, another copying of the list is required during process_create which duplicates the list found in the new process’s struct process_permissions to the process list of fs_allowances.
In Summary...
This is pretty much the overview of what is going on now and hopefully this explanation along with comments within the code will give you an understanding of the implementation of some of the file system code so that you can modify it or at least have an idea of what is going on when a user process accesses the filesystem. If you are interested in helping, here’s a list of things that might be next for Nunya’s file system:
- Bring the ability to read and write to directories up to user level (read ability exists in iso. This is currently preventing simple binaries such as ls from being written
- Create the aforementioned ata table which identifies the type of filesystem on a given ata unit and can act as a map from ata unit to appropriate filesystem code
- Implement a writable file system to be used on a hard drive
- Create low level code for other standard file systems to plug into the bottom of fs_agnostic_file