Introduction
The e file system which is named after the Euler
constant e = 2.718 is a basic file system that aims to support a UNIX
like interface. The system is currently designed to run in user space where all
data is stored in a single file despite it representing multiple files and
directories. The system currently supports most UNIX system calls such as open,
close, read, write, and lseek. It can be used for archiving files as all reads
and writes occur on one actual file in the underlying system. Additionally, it
can be used to stream data out in a client-server environment. Further, it can
be used as a file system for a hobbyist operating system or embedded operating
system.
Background
The basic design of the e File System is the
focus on a single block of 512 bytes. While this can be configured to be a
larger constant, all reads and writes are based on this block size which in the
code is represented by the C macro DEV_BLOCK_SIZE
. The system sets aside
approximately 20 percent of the blocks for inodes and the remaining 80 percent
for data. The initial space in the available file system is currently reserved
for loading a kernel and represents the first 0 to 4095 blocks, or two
megabytes. The first block is block 4096 which is the root block describing the
file system. Its a C structure name master_inode
whose definition is :=
typedef struct master_inode {
short magic;
short init_fs;
block_t block_start;
block_t inode_map_start;
block_t inode_map_end;
block_t bmap_map_start;
block_t bmap_map_end;
block_t inode_start;
block_t inode_end;
block_t data_start;
block_t data_end;
block_t imap_ptr;
block_t imap_bit;
block_t bmap_ptr;
block_t bmap_bit;
block_t inodes;
block_t blocks;
char *imap;
char *bmap;
block_t dev;
char pad[MNODE_PAD];
} master_inode_t;
The master_inode
also known as a root inode and describes
the layout of the file system on disk. It indicates where the inodes
start,
where the data nodes start, and where the bitmaps start that mark which blocks
are free or in use. The next and most important structure is the inode
structure which is a C structure defined as follows:
typedef struct inode {
char user_read;
char user_write;
char user_execute;
char group_read;
char group_write;
char group_execute;
char world_read;
char world_write;
char world_execute;
char is_directory;
char is_device;
char is_file;
char is_symlink;
char is_hardlink;
block_t create_time;
block_t modified_time;
block_t accessed_time;
block_t self;
block_t parent;
inode_ptr_t size;
inode_ptr_t pos;
block_t current;
block_t current_parent;
int iblock;
int dev;
inode_perm_t o_mode;
inode_group_t group;
inode_own_t owner;
block_t refcount;
char path[MAX_PATH];
char pad[INODE_PAD];
block_t next;
} inode_t;
The inode
structure like the master_inode
are 512 bytes
each. The inode
describes a file or directory entry point. If it is a file, the
next pointer points to data blocks and if it is a directory, the next pointer
points to other inodes. In both cases, the next pointer is pointing to a C
structure called block_map which contains 127 block pointers and one next
pointer. The block pointers can be data or inodes. All blocks are fixed size
512 bytes. The block_map
structure is defined as:
typedef struct block_map {
block_t next;
block_t blocks[BMAP_BLOCKS];
} block_map_t;
Symbolic links are implemented as a single 512 byte block
which is pointed to by the next pointer in the inode and containing a path of
another file or directory. Hardlinks are implemented similarly but have a
reference count associated with them. The structure for a link is
defined as:
typedef struct link {
char path[MAX_PATH];
char pad[MAX_PATH];
} link_t;
The most important functions in the implementation
are inode_create
, inode_get
, and inode_free
. These
functions along with other file system functions make use of the block buffer
cache. The inode_create function creates files, directories, symbolic links,
and hard links. The inode_get
function retrieves an inode representing either a
file, directory, symbolic link, or hard link from the file system. The
inode_free
function releases the resources of a file, directory, symbolic link,
or hard link. In the case of directories, the inode_free
function will fail if
the directory is not empty.
Paths in the system are represented using UNIX
style paths where the path separator is the “/” forward slash character. The
root of the file system is represented as a single forward slash. The current
directory is represented by the special character “.”. The parent directory is
represented by the special characters “..”.
The implementation was done using a layered
approach where the most basic components were implemented first. All components
were tested using test drivers written in C and are a part of their respective
.c implementation files. The most low-level and basic modules are bitmap.c for
implementing bitmaps, paths.c for UNIX path manipulation, and dev.c for reading
from disk or writing out to disk a specified block. The next level is the block
buffer cache which is contained in block.c. The heart of the system is then
inode.c which implements all inode functionality. The file system system calls
that refer to a file are in file.c. These include familiar open, read, write,
close, and lseek calls. Directory system calls are in dir.c. Link system calls
are in link.c. A utility module called krealpath.c provides a function to
resolve relative paths to absolute ones as all functions actually work on
absolute paths. In order for the system to work in user space, some kernel-like
structures are needed and these are contained in compat.h and compat.c.
Using the Code
The code can not compile and run in user
space without prefixing the system calls. Without a prefix, it is likely that
the local implementation of the system calls will actually resolve the
underlying kernel's. Therefore, the letter “k” is prefixed to all calls and the
external interface is accessible in C programs using fs_syscalls.h. For
example, if you wish to open a file, then you would use the kopen call instead
of the open system call. Before actually using the file system, you need to
initialize it. The code to do this is as follows:
#include "bool.h"
#include "paths.h"
#include "block.h"
#include "dev.h"
#include "inode.h"
#ifdef _TEST_INC
#include <stdio.h>
#include <string.h>
#define printk printf
#include "compat.h"
#endif
#include "file.h"
#include "dir.h"
#include "link.h"
if(inode_dev_open("./inode.dat",&dev) == INODE_INIT) {
if(inode_mkfs("./inode.dat", 2 * TWOMEG) != INODE_OK) {
printk("fs_init:: error initializing filesystem\n");
return 1;
}
if(inode_dev_open("./inode.dat", &dev) != INODE_OK) {
printk("fs_init:: error opening device\n");
return 1;
}
}
master = master_get(dev);
master_set_dev("./inode.dat", dev);
The code first checks if the file system can be opened in
the call to inode_dev_open
, if not, then it makes the file system using
the call inode_mkfs
. Once the file system is created, a second call to inode_dev_open
is needed to open the file system. Afterwords, the master inode needs to be
initialized and the calls to master_get
and master_set_dev
do
that. After these calls, the file system can be used. For example:
if((fd = kopen("/foo", O_CREAT | O_RDWR)) == -1) {
printk("error opening file\n");
}
Points of Interest
The system is part of hobbyist operating
system that is being developed. Users interested in working on an open source
kernel intended for embedded and experimental use are encouraged to contact the
author. As it stands, the file system was tested using test drivers that are
part of the respective C source files and test a specific module. The
accompanying Makefile builds various test programs to test out the code. Since
no user has used the code, the code should be considered as a beta version.
Users are encouraged to use the code and improve on it.