Update markdown and tabs and stuff
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
layout: post
|
||||
title: "Virtual File System 2"
|
||||
subtitle: "for real this time."
|
||||
tags: [osdev]
|
||||
tags: [osdev, filesystems]
|
||||
|
||||
Once again, several months have passed since I wrote anything here.
|
||||
I also worked very little on the kernel during this time, though. So no
|
||||
@@ -33,25 +33,26 @@ generated on-the-fly by the kernel, network connections(?) etc.
|
||||
|
||||
Further, I want the VFS to be independent of any disk file system, e.g.
|
||||
the VFS shouldn't have user-group-other read-write-execute tuples just
|
||||
because it's designed with ext2 in mind. It might have those tuples -
|
||||
because it's designed with ext2 in mind. It might have those tuples -
|
||||
I haven't decided yet - but it it does it won't be because ext2 uses
|
||||
them. Nor will the VFS be designed with ext2 or any other disk file
|
||||
system in mind.
|
||||
|
||||
The VFS should offer the functions
|
||||
|
||||
open() // Open or create a file
|
||||
close() // Close a file
|
||||
read() // Read data from opened file
|
||||
write() // Write data to opened file
|
||||
move() // Move a file or directory
|
||||
link() // Put a file or directory in path tree
|
||||
unlink() // Remove a file or directory from path tree
|
||||
stat() // Get more info about a file or directory
|
||||
isatty() // Returns true if the file is a terminal
|
||||
mkdir() // Create a directory
|
||||
readdir() // Get a directory entry
|
||||
finddir() // Find a file by name from a directory
|
||||
:::c
|
||||
open() // Open or create a file
|
||||
close() // Close a file
|
||||
read() // Read data from opened file
|
||||
write() // Write data to opened file
|
||||
move() // Move a file or directory
|
||||
link() // Put a file or directory in path tree
|
||||
unlink() // Remove a file or directory from path tree
|
||||
stat() // Get more info about a file or directory
|
||||
isatty() // Returns true if the file is a terminal
|
||||
mkdir() // Create a directory
|
||||
readdir() // Get a directory entry
|
||||
finddir() // Find a file by name from a directory
|
||||
|
||||
for all files and directories regardless of their underlying device or
|
||||
driver.
|
||||
@@ -96,20 +97,18 @@ Example:
|
||||
A good starting point for the inode structure might be some pointers to
|
||||
allow it to be placed in a tree, then.
|
||||
|
||||
:::c
|
||||
struct vfs_node_st;
|
||||
typedef vfs_node_t * INODE;
|
||||
|
||||
struct vfs_node_st;
|
||||
typedef vfs_node_t * INODE;
|
||||
|
||||
typedef struct vfs_node_st
|
||||
{
|
||||
char name[VFS_NAME_SZ];
|
||||
INODE parent;
|
||||
INODE child;
|
||||
INODE older, younger;
|
||||
uint32_t type;
|
||||
} vfs_node_t;
|
||||
|
||||
|
||||
typedef struct vfs_node_st
|
||||
{
|
||||
char name[VFS_NAME_SZ];
|
||||
INODE parent;
|
||||
INODE child;
|
||||
INODE older, younger;
|
||||
uint32_t type;
|
||||
} vfs_node_t;
|
||||
|
||||
This does waste a bit of memory, since most inodes that are used by the
|
||||
system won't be in the VFS tree, but four `size_t` isn't that much,
|
||||
@@ -125,113 +124,118 @@ to maintain.
|
||||
Then, we need a way to keep track of the driver, i.e. the functions
|
||||
called to access the file. To do this, I define a new struct:
|
||||
|
||||
typedef struct vfs_driver_st
|
||||
{
|
||||
uint32_t (*open)(INODE, uint32_t);
|
||||
uint32_t (*close)(INODE);
|
||||
uint32_t (*read)(INODE, void *, uint32_t, uint32_t);
|
||||
uint32_t (*write)(INODE, void *, uint32_t, uint32_t);
|
||||
uint32_t (*link)(INODE, INODE, const char *);
|
||||
uint32_t (*unlink)(INODE, const char *);
|
||||
uint32_t (*stat)(INODE, struct stat *st);
|
||||
uint32_t (*isatty)(INODE);
|
||||
uint32_t (*mkdir)(INODE, const char *);
|
||||
dirent_t *(*readdir)(INODE, uint32_t);
|
||||
INODE (*finddir)(INODE, const char *);
|
||||
} vfs_driver_t;
|
||||
:::c
|
||||
typedef struct vfs_driver_st
|
||||
{
|
||||
uint32_t (*open)(INODE, uint32_t);
|
||||
uint32_t (*close)(INODE);
|
||||
uint32_t (*read)(INODE, void *, uint32_t, uint32_t);
|
||||
uint32_t (*write)(INODE, void *, uint32_t, uint32_t);
|
||||
uint32_t (*link)(INODE, INODE, const char *);
|
||||
uint32_t (*unlink)(INODE, const char *);
|
||||
uint32_t (*stat)(INODE, struct stat *st);
|
||||
uint32_t (*isatty)(INODE);
|
||||
uint32_t (*mkdir)(INODE, const char *);
|
||||
dirent_t *(*readdir)(INODE, uint32_t);
|
||||
INODE (*finddir)(INODE, const char *);
|
||||
} vfs_driver_t;
|
||||
|
||||
and add `vfs_driver_t *d` to the inode struct. I also added a length
|
||||
value, a void pointer for arbitrary data used by the drivers and a flags
|
||||
value - also for use by the drivers. The
|
||||
inode struct now looks like this:
|
||||
|
||||
typedef struct vfs_node_st
|
||||
{
|
||||
char name[VFS_NAME_SZ];
|
||||
void *parent;
|
||||
void *child;
|
||||
void *older, *younger;
|
||||
uint32_t type;
|
||||
vfs_driver_t *d;
|
||||
void *data;
|
||||
uint32_t flags;
|
||||
uint32_t length;
|
||||
}
|
||||
:::c
|
||||
typedef struct vfs_node_st
|
||||
{
|
||||
char name[VFS_NAME_SZ];
|
||||
void *parent;
|
||||
void *child;
|
||||
void *older, *younger;
|
||||
uint32_t type;
|
||||
vfs_driver_t *d;
|
||||
void *data;
|
||||
uint32_t flags;
|
||||
uint32_t length;
|
||||
}
|
||||
|
||||
###Vfs functions
|
||||
Next, I create some wrapper functions to call the driver functions.
|
||||
|
||||
uint32_t vfs_open(INODE ino, uint32_t mode)
|
||||
{
|
||||
if(ino->d->open)
|
||||
return ino->d->open(ino, mode);
|
||||
return 0;
|
||||
}
|
||||
:::c
|
||||
uint32_t vfs_open(INODE ino, uint32_t mode)
|
||||
{
|
||||
if(ino->d->open)
|
||||
return ino->d->open(ino, mode);
|
||||
return 0;
|
||||
}
|
||||
|
||||
and similar for all functions except `readdir` and `finddir` which
|
||||
contain code to handle `.` and `..` for mount roots.
|
||||
|
||||
|
||||
dirent_t *vfs_readdir(INODE ino, uint32_t num)
|
||||
{
|
||||
if(ino->type & FS_MOUNT)
|
||||
{
|
||||
if(num == 0)
|
||||
{
|
||||
dirent_t *ret = calloc(1, sizeof(dirent_t));
|
||||
ret->ino = ino;
|
||||
strcpy(ret->name, ".");
|
||||
return ret;
|
||||
} else if(num == 1) {
|
||||
dirent_t *ret = calloc(1, sizeof(dirent_t));
|
||||
ret->ino = ino->parent;
|
||||
strcpy(ret->name, "..");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
if(ino->d->readdir)
|
||||
return ino->d->readdir(ino, num);
|
||||
return 0;
|
||||
}
|
||||
:::c
|
||||
dirent_t *vfs_readdir(INODE ino, uint32_t num)
|
||||
{
|
||||
if(ino->type & FS_MOUNT)
|
||||
{
|
||||
if(num == 0)
|
||||
{
|
||||
dirent_t *ret = calloc(1, sizeof(dirent_t));
|
||||
ret->ino = ino;
|
||||
strcpy(ret->name, ".");
|
||||
return ret;
|
||||
} else if(num == 1) {
|
||||
dirent_t *ret = calloc(1, sizeof(dirent_t));
|
||||
ret->ino = ino->parent;
|
||||
strcpy(ret->name, "..");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
if(ino->d->readdir)
|
||||
return ino->d->readdir(ino, num);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
INODE vfs_finddir(INODE ino, const char *name)
|
||||
{
|
||||
if(ino->type & FS_MOUNT)
|
||||
{
|
||||
if(!strcmp(name, "."))
|
||||
{
|
||||
return ino;
|
||||
} else if(!strcmp(name, "..")) {
|
||||
return ino->parent;
|
||||
}
|
||||
}
|
||||
if(ino->d->finddir)
|
||||
return ino->d->finddir(ino, name);
|
||||
if(ino->d->readdir)
|
||||
{
|
||||
// Backup solution
|
||||
int num = 0;
|
||||
dirent_t *de;
|
||||
while(1)
|
||||
{
|
||||
de = vfs_readdir(ino, num);
|
||||
if(!de)
|
||||
return 0;
|
||||
if(!strcmp(name, de->name))
|
||||
break;
|
||||
free(de->name);
|
||||
free(de);
|
||||
num++;
|
||||
}
|
||||
INODE ret = de->ino;
|
||||
free(de->name);
|
||||
free(de);
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
:::c
|
||||
INODE vfs_finddir(INODE ino, const char *name)
|
||||
{
|
||||
if(ino->type & FS_MOUNT)
|
||||
{
|
||||
if(!strcmp(name, "."))
|
||||
{
|
||||
return ino;
|
||||
} else if(!strcmp(name, "..")) {
|
||||
return ino->parent;
|
||||
}
|
||||
}
|
||||
if(ino->d->finddir)
|
||||
return ino->d->finddir(ino, name);
|
||||
if(ino->d->readdir)
|
||||
{
|
||||
// Backup solution
|
||||
int num = 0;
|
||||
dirent_t *de;
|
||||
while(1)
|
||||
{
|
||||
de = vfs_readdir(ino, num);
|
||||
if(!de)
|
||||
return 0;
|
||||
if(!strcmp(name, de->name))
|
||||
break;
|
||||
free(de->name);
|
||||
free(de);
|
||||
num++;
|
||||
}
|
||||
INODE ret = de->ino;
|
||||
free(de->name);
|
||||
free(de);
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Finally, I needed a function for mounting filesystems in the mount tree
|
||||
and the `namei` function, which can actually be combined since they both
|
||||
@@ -241,38 +245,39 @@ _Warning:_ Pointer-pointers ahead!
|
||||
|
||||
First: a function for traversing the mount tree as far as possible
|
||||
|
||||
INODE vfs_find_root(char **path)
|
||||
{
|
||||
// Find closest point in mount tree
|
||||
INODE current = vfs_root;
|
||||
INODE mount = current;
|
||||
char *name;
|
||||
while((name = strsep(path, "/")))
|
||||
{
|
||||
current = current->child;
|
||||
while(current)
|
||||
{
|
||||
if(!strcmp(current->name, name))
|
||||
{
|
||||
mount = current;
|
||||
break;
|
||||
}
|
||||
current = current->olderyounger;
|
||||
}
|
||||
if(!current)
|
||||
{
|
||||
if(*path)
|
||||
{
|
||||
*path = *path - 1;
|
||||
*path[0] = '/';
|
||||
}
|
||||
*path = name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (INODE)mount;
|
||||
}
|
||||
:::c
|
||||
INODE vfs_find_root(char **path)
|
||||
{
|
||||
// Find closest point in mount tree
|
||||
INODE current = vfs_root;
|
||||
INODE mount = current;
|
||||
char *name;
|
||||
while((name = strsep(path, "/")))
|
||||
{
|
||||
current = current->child;
|
||||
while(current)
|
||||
{
|
||||
if(!strcmp(current->name, name))
|
||||
{
|
||||
mount = current;
|
||||
break;
|
||||
}
|
||||
current = current->olderyounger;
|
||||
}
|
||||
if(!current)
|
||||
{
|
||||
if(*path)
|
||||
{
|
||||
*path = *path - 1;
|
||||
*path[0] = '/';
|
||||
}
|
||||
*path = name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (INODE)mount;
|
||||
}
|
||||
|
||||
Pretty self explanatory. No? Well, `strsep` is a library function which
|
||||
picks out one part of the path at a time and also advances the `path`
|
||||
@@ -284,69 +289,69 @@ The namei/mount function then uses this as a starting point:
|
||||
|
||||
|
||||
|
||||
INODE vfs_namei_mount(const char *path, INODE root)
|
||||
{
|
||||
char *npath = strdup(path);
|
||||
char *pth = &npath[1];
|
||||
// Find closest point in mount tree
|
||||
INODE current = vfs_find_root(&pth);
|
||||
char *name;
|
||||
while(current && (name = strsep(&pth, "/")))
|
||||
{
|
||||
// Go through the path
|
||||
INODE next = vfs_finddir(current, name);
|
||||
|
||||
if(root)
|
||||
{
|
||||
// If we want to mount someting
|
||||
if(!next)
|
||||
{
|
||||
// Create last part of path if it doesn't exist
|
||||
// But only if it is the last part.
|
||||
if(pth)
|
||||
return 0;
|
||||
next = calloc(1, sizeof(vfs_node_t));
|
||||
strcpy(next->name, name);
|
||||
next->type = FS_DIRECTORY;
|
||||
}
|
||||
|
||||
// Add path to mount tree
|
||||
next->parent = current;
|
||||
next->older = current->child;
|
||||
current->child = next;
|
||||
}
|
||||
|
||||
if(!next)
|
||||
return 0;
|
||||
if(!current->parent)
|
||||
free(current);
|
||||
|
||||
current = next;
|
||||
}
|
||||
free(npath);
|
||||
|
||||
if(root && current->type == FS_DIRECTORY)
|
||||
{
|
||||
// Replace node in mount tree
|
||||
root->parent = current->parent;
|
||||
if(root->parent->child == current)
|
||||
root->parent->child = root;
|
||||
root->older = current->older;
|
||||
if(root->older)
|
||||
root->older->younger = current;
|
||||
root->younger = current->younger;
|
||||
if(root->younger)
|
||||
root->younger->older = current;
|
||||
strcpy(root->name, current->name);
|
||||
root->type = FS_MOUNT;
|
||||
if(current == vfs_root)
|
||||
vfs_root = root;
|
||||
|
||||
free(current);
|
||||
}
|
||||
return current;
|
||||
}
|
||||
{: .lang-c}
|
||||
:::c
|
||||
INODE vfs_namei_mount(const char *path, INODE root)
|
||||
{
|
||||
char *npath = strdup(path);
|
||||
char *pth = &npath[1];
|
||||
// Find closest point in mount tree
|
||||
INODE current = vfs_find_root(&pth);
|
||||
char *name;
|
||||
while(current && (name = strsep(&pth, "/")))
|
||||
{
|
||||
// Go through the path
|
||||
INODE next = vfs_finddir(current, name);
|
||||
|
||||
if(root)
|
||||
{
|
||||
// If we want to mount someting
|
||||
if(!next)
|
||||
{
|
||||
// Create last part of path if it doesn't exist
|
||||
// But only if it is the last part.
|
||||
if(pth)
|
||||
return 0;
|
||||
next = calloc(1, sizeof(vfs_node_t));
|
||||
strcpy(next->name, name);
|
||||
next->type = FS_DIRECTORY;
|
||||
}
|
||||
|
||||
// Add path to mount tree
|
||||
next->parent = current;
|
||||
next->older = current->child;
|
||||
current->child = next;
|
||||
}
|
||||
|
||||
if(!next)
|
||||
return 0;
|
||||
if(!current->parent)
|
||||
free(current);
|
||||
|
||||
current = next;
|
||||
}
|
||||
free(npath);
|
||||
|
||||
if(root && current->type == FS_DIRECTORY)
|
||||
{
|
||||
// Replace node in mount tree
|
||||
root->parent = current->parent;
|
||||
if(root->parent->child == current)
|
||||
root->parent->child = root;
|
||||
root->older = current->older;
|
||||
if(root->older)
|
||||
root->older->younger = current;
|
||||
root->younger = current->younger;
|
||||
if(root->younger)
|
||||
root->younger->older = current;
|
||||
strcpy(root->name, current->name);
|
||||
root->type = FS_MOUNT;
|
||||
if(current == vfs_root)
|
||||
vfs_root = root;
|
||||
|
||||
free(current);
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
Note how `pth` is changed by `vfs_find_root()` to only contain the part
|
||||
of the path that wasn't found. After that, we ask each node for the next
|
||||
@@ -360,46 +365,47 @@ the final inode is returned.
|
||||
I also made two simple wrappers for this function:
|
||||
|
||||
|
||||
INODE vfs_namei(const char *path)
|
||||
{
|
||||
return vfs_namei_mount(path, 0);
|
||||
}
|
||||
|
||||
INODE vfs_mount(const char *path, INODE root)
|
||||
{
|
||||
return vfs_namei_mount(path, root);
|
||||
}
|
||||
{: .lang-c}
|
||||
:::c
|
||||
INODE vfs_namei(const char *path)
|
||||
{
|
||||
return vfs_namei_mount(path, 0);
|
||||
}
|
||||
|
||||
INODE vfs_mount(const char *path, INODE root)
|
||||
{
|
||||
return vfs_namei_mount(path, root);
|
||||
}
|
||||
|
||||
And finally, a function for unmounting file systems:
|
||||
|
||||
|
||||
INODE vfs_umount(const char *path)
|
||||
{
|
||||
char *npath = strdup(path);
|
||||
char *pth = &npath[1];
|
||||
INODE ino = vfs_find_root(&pth);
|
||||
if(!ino || pth)
|
||||
{
|
||||
free(npath);
|
||||
return 0;
|
||||
}
|
||||
if(ino->child)
|
||||
{
|
||||
free(npath);
|
||||
return 0;
|
||||
} else {
|
||||
// Remove node from mount tree
|
||||
if(ino->parent->child == ino)
|
||||
ino->parent->child = ino->older;
|
||||
if(ino->younger)
|
||||
ino->younger->older = ino->older;
|
||||
if(ino->older)
|
||||
ino->older->younger = ino->younger;
|
||||
free(npath);
|
||||
return ino;
|
||||
}
|
||||
}
|
||||
:::c
|
||||
INODE vfs_umount(const char *path)
|
||||
{
|
||||
char *npath = strdup(path);
|
||||
char *pth = &npath[1];
|
||||
INODE ino = vfs_find_root(&pth);
|
||||
if(!ino || pth)
|
||||
{
|
||||
free(npath);
|
||||
return 0;
|
||||
}
|
||||
if(ino->child)
|
||||
{
|
||||
free(npath);
|
||||
return 0;
|
||||
} else {
|
||||
// Remove node from mount tree
|
||||
if(ino->parent->child == ino)
|
||||
ino->parent->child = ino->older;
|
||||
if(ino->younger)
|
||||
ino->younger->older = ino->older;
|
||||
if(ino->older)
|
||||
ino->older->younger = ino->younger;
|
||||
free(npath);
|
||||
return ino;
|
||||
}
|
||||
}
|
||||
|
||||
And that's it for now. A lot of code this time, but that's because I
|
||||
don't want to push my changes to github quite yet, so I can't give you a
|
||||
|
||||
Reference in New Issue
Block a user