502 lines
10 KiB
C
502 lines
10 KiB
C
/*
|
|
Copyright (C) 2016-2019 The University of Notre Dame
|
|
This software is distributed under the GNU General Public License.
|
|
See the file LICENSE for details.
|
|
*/
|
|
|
|
#include "fs.h"
|
|
#include "fs_internal.h"
|
|
#include "kmalloc.h"
|
|
#include "string.h"
|
|
#include "page.h"
|
|
#include "process.h"
|
|
#include "bcache.h"
|
|
|
|
static struct fs *fs_list = 0;
|
|
|
|
static struct kobject * find_kobject_by_tag( const char *tag )
|
|
{
|
|
int i;
|
|
|
|
// Check if tag is index-specified.
|
|
if(tag[0] == '#') {
|
|
str2int(&tag[1], &i);
|
|
return current->ktable[i];
|
|
} else {
|
|
// Find an tag matching the tag.
|
|
int max = process_object_max(current);
|
|
for(i=0;i<max;i++) {
|
|
struct kobject *k = current->ktable[i];
|
|
if(k && !strcmp(k->tag,tag)) {
|
|
return k;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct fs_dirent * fs_getroot( struct process *p )
|
|
{
|
|
struct kobject *k = p->ktable[KNO_STDDIR];
|
|
if( k && k->type==KOBJECT_DIR ) {
|
|
return k->data.dir;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
struct fs_dirent * fs_getcurrent( struct process *p )
|
|
{
|
|
struct kobject *k = p->ktable[KNO_STDDIR];
|
|
if( k && k->type==KOBJECT_DIR ) {
|
|
return k->data.dir;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
struct fs_dirent *fs_resolve(const char *path)
|
|
{
|
|
// If the path begins with a slash, navigate from the root directory.
|
|
if(path[0] == '/') {
|
|
return fs_dirent_traverse(fs_getroot(current), &path[1]);
|
|
}
|
|
|
|
// If the path contains a colon, we are dealing with a tag.
|
|
const char *colon = strchr(path,':');
|
|
if(colon) {
|
|
// Length of tag is distance from colon to beginning.
|
|
int length = colon - path;
|
|
|
|
// Rest of path starts after the colon.
|
|
const char *rest = colon+1;
|
|
|
|
// Make a temporary string with the tag.
|
|
char *tagstr = strdup(path);
|
|
tagstr[length] = 0;
|
|
|
|
// Look up the object associated with that tag
|
|
struct kobject *tagobj = find_kobject_by_tag(tagstr);
|
|
kfree(tagstr);
|
|
if(!tagobj) return 0;
|
|
// XXX KERROR_NOT_FOUND;
|
|
|
|
// Make sure it is really a directory.
|
|
if(kobject_get_type(tagobj)!=KOBJECT_DIR) return 0;
|
|
// XXX KERROR_NOT_A_DIRECTORY;
|
|
|
|
// If there is no remaining path, just return that object.
|
|
if(!*rest) return fs_dirent_addref(tagobj->data.dir);
|
|
|
|
// Otherwise, navigate from that object.
|
|
return fs_dirent_traverse(tagobj->data.dir,path);
|
|
}
|
|
|
|
// If there was no tag, then navigate from the current working directory.
|
|
return fs_dirent_traverse(fs_getcurrent(current), path);
|
|
}
|
|
|
|
void fs_register(struct fs *f)
|
|
{
|
|
f->next = fs_list;
|
|
fs_list = f;
|
|
}
|
|
|
|
struct fs *fs_lookup(const char *name)
|
|
{
|
|
struct fs *f;
|
|
|
|
for(f = fs_list; f; f = f->next) {
|
|
if(!strcmp(name, f->name)) {
|
|
return f;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int fs_volume_format(struct fs *f, struct device *d )
|
|
{
|
|
const struct fs_ops *ops = f->ops;
|
|
if(!ops->volume_format)
|
|
return KERROR_NOT_IMPLEMENTED;
|
|
return f->ops->volume_format(d);
|
|
}
|
|
|
|
struct fs_volume *fs_volume_open(struct fs *f, struct device *d )
|
|
{
|
|
const struct fs_ops *ops = f->ops;
|
|
|
|
if(!ops->volume_open)
|
|
return 0;
|
|
|
|
struct fs_volume *v = f->ops->volume_open(d);
|
|
if(v) {
|
|
v->fs = f;
|
|
v->device = device_addref(d);
|
|
}
|
|
return v;
|
|
}
|
|
|
|
struct fs_volume *fs_volume_addref(struct fs_volume *v)
|
|
{
|
|
v->refcount++;
|
|
return v;
|
|
}
|
|
|
|
int fs_volume_close(struct fs_volume *v)
|
|
{
|
|
const struct fs_ops *ops = v->fs->ops;
|
|
if(!ops->volume_close)
|
|
return KERROR_NOT_IMPLEMENTED;
|
|
|
|
v->refcount--;
|
|
if(v->refcount==0) {
|
|
v->fs->ops->volume_close(v);
|
|
bcache_flush_device(v->device);
|
|
device_close(v->device);
|
|
kfree(v);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct fs_dirent *fs_volume_root(struct fs_volume *v)
|
|
{
|
|
const struct fs_ops *ops = v->fs->ops;
|
|
if(!ops->volume_root)
|
|
return 0;
|
|
|
|
struct fs_dirent *d = v->fs->ops->volume_root(v);
|
|
d->volume = fs_volume_addref(v);
|
|
return d;
|
|
}
|
|
|
|
int fs_dirent_list(struct fs_dirent *d, char *buffer, int buffer_length)
|
|
{
|
|
const struct fs_ops *ops = d->volume->fs->ops;
|
|
if(!ops->list)
|
|
return KERROR_NOT_IMPLEMENTED;
|
|
return ops->list(d, buffer, buffer_length);
|
|
}
|
|
|
|
static struct fs_dirent *fs_dirent_lookup(struct fs_dirent *d, const char *name)
|
|
{
|
|
const struct fs_ops *ops = d->volume->fs->ops;
|
|
|
|
if(!ops->lookup)
|
|
return 0;
|
|
|
|
if(!strcmp(name,".")) {
|
|
// Special case: . refers to the containing directory.
|
|
return fs_dirent_addref(d);
|
|
} else {
|
|
struct fs_dirent *r = ops->lookup(d, name);
|
|
if(r) r->volume = fs_volume_addref(d->volume);
|
|
return r;
|
|
}
|
|
}
|
|
|
|
struct fs_dirent *fs_dirent_traverse(struct fs_dirent *parent, const char *path)
|
|
{
|
|
if(!parent || !path)
|
|
return 0;
|
|
|
|
char *lpath = kmalloc(strlen(path) + 1);
|
|
strcpy(lpath, path);
|
|
|
|
struct fs_dirent *d = parent;
|
|
|
|
char *part = strtok(lpath, "/");
|
|
while(part) {
|
|
struct fs_dirent *n = fs_dirent_lookup(d, part);
|
|
|
|
if(d!=parent) fs_dirent_close(d);
|
|
|
|
if(!n) {
|
|
// KERROR_NOT_FOUND
|
|
kfree(lpath);
|
|
return 0;
|
|
}
|
|
d = n;
|
|
part = strtok(0, "/");
|
|
}
|
|
kfree(lpath);
|
|
return d;
|
|
}
|
|
|
|
struct fs_dirent *fs_dirent_addref(struct fs_dirent *d)
|
|
{
|
|
d->refcount++;
|
|
return d;
|
|
}
|
|
|
|
int fs_dirent_close(struct fs_dirent *d)
|
|
{
|
|
const struct fs_ops *ops = d->volume->fs->ops;
|
|
if(!ops->close)
|
|
return KERROR_NOT_IMPLEMENTED;
|
|
|
|
d->refcount--;
|
|
if(d->refcount==0) {
|
|
ops->close(d);
|
|
// This close is paired with the addref in fs_dirent_lookup
|
|
fs_volume_close(d->volume);
|
|
kfree(d);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int fs_dirent_read(struct fs_dirent *d, char *buffer, uint32_t length, uint32_t offset)
|
|
{
|
|
int total = 0;
|
|
int bs = d->volume->block_size;
|
|
|
|
const struct fs_ops *ops = d->volume->fs->ops;
|
|
if(!ops->read_block)
|
|
return KERROR_INVALID_REQUEST;
|
|
|
|
if(offset > d->size) {
|
|
return 0;
|
|
}
|
|
|
|
if(offset + length > d->size) {
|
|
length = d->size - offset;
|
|
}
|
|
|
|
char *temp = page_alloc(0);
|
|
if(!temp)
|
|
return -1;
|
|
|
|
while(length > 0) {
|
|
|
|
int blocknum = offset / bs;
|
|
int actual = 0;
|
|
|
|
if(offset % bs) {
|
|
actual = ops->read_block(d, temp, blocknum);
|
|
if(actual != bs)
|
|
goto failure;
|
|
actual = MIN(bs - offset % bs, length);
|
|
memcpy(buffer, &temp[offset % bs], actual);
|
|
} else if(length >= bs) {
|
|
actual = ops->read_block(d, buffer, blocknum);
|
|
if(actual != bs)
|
|
goto failure;
|
|
} else {
|
|
actual = ops->read_block(d, temp, blocknum);
|
|
if(actual != bs)
|
|
goto failure;
|
|
actual = length;
|
|
memcpy(buffer, temp, actual);
|
|
}
|
|
|
|
buffer += actual;
|
|
length -= actual;
|
|
offset += actual;
|
|
total += actual;
|
|
}
|
|
|
|
page_free(temp);
|
|
return total;
|
|
|
|
failure:
|
|
page_free(temp);
|
|
if(total == 0)
|
|
return -1;
|
|
return total;
|
|
}
|
|
|
|
struct fs_dirent * fs_dirent_mkdir(struct fs_dirent *d, const char *name)
|
|
{
|
|
const struct fs_ops *ops = d->volume->fs->ops;
|
|
if(!ops->mkdir) return 0;
|
|
|
|
struct fs_dirent *n = ops->mkdir(d, name);
|
|
if(n) {
|
|
n->volume = fs_volume_addref(d->volume);
|
|
return n;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct fs_dirent * fs_dirent_mkfile(struct fs_dirent *d, const char *name)
|
|
{
|
|
const struct fs_ops *ops = d->volume->fs->ops;
|
|
if(!ops->mkfile) return 0;
|
|
|
|
struct fs_dirent *n = ops->mkfile(d, name);
|
|
if(n) {
|
|
n->volume = fs_volume_addref(d->volume);
|
|
return n;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int fs_dirent_remove(struct fs_dirent *d, const char *name)
|
|
{
|
|
const struct fs_ops *ops = d->volume->fs->ops;
|
|
if(!ops->remove)
|
|
return 0;
|
|
return ops->remove(d, name);
|
|
}
|
|
|
|
int fs_dirent_write(struct fs_dirent *d, const char *buffer, uint32_t length, uint32_t offset)
|
|
{
|
|
int total = 0;
|
|
int bs = d->volume->block_size;
|
|
|
|
const struct fs_ops *ops = d->volume->fs->ops;
|
|
if(!ops->write_block || !ops->read_block)
|
|
return KERROR_INVALID_REQUEST;
|
|
|
|
char *temp = page_alloc(0);
|
|
|
|
// if writing past the (current) end of the file, resize the file first
|
|
if (offset + length > d->size) {
|
|
ops->resize(d, offset+length);
|
|
}
|
|
|
|
while(length > 0) {
|
|
|
|
int blocknum = offset / bs;
|
|
int actual = 0;
|
|
|
|
if(offset % bs) {
|
|
actual = ops->read_block(d, temp, blocknum);
|
|
if(actual != bs)
|
|
goto failure;
|
|
|
|
actual = MIN(bs - offset % bs, length);
|
|
memcpy(&temp[offset % bs], buffer, actual);
|
|
|
|
int wactual = ops->write_block(d, temp, blocknum);
|
|
if(wactual != bs)
|
|
goto failure;
|
|
|
|
} else if(length >= bs) {
|
|
actual = ops->write_block(d, buffer, blocknum);
|
|
if(actual != bs)
|
|
goto failure;
|
|
} else {
|
|
actual = ops->read_block(d, temp, blocknum);
|
|
if(actual != bs)
|
|
goto failure;
|
|
|
|
actual = length;
|
|
memcpy(temp, buffer, actual);
|
|
|
|
int wactual = ops->write_block(d, temp, blocknum);
|
|
if(wactual != bs)
|
|
goto failure;
|
|
}
|
|
|
|
buffer += actual;
|
|
length -= actual;
|
|
offset += actual;
|
|
total += actual;
|
|
}
|
|
|
|
page_free(temp);
|
|
return total;
|
|
|
|
failure:
|
|
page_free(temp);
|
|
if(total == 0)
|
|
return -1;
|
|
return total;
|
|
}
|
|
|
|
int fs_dirent_size(struct fs_dirent *d)
|
|
{
|
|
return d->size;
|
|
}
|
|
|
|
int fs_dirent_isdir( struct fs_dirent *d )
|
|
{
|
|
return d->isdir;
|
|
}
|
|
|
|
int fs_dirent_copy(struct fs_dirent *src, struct fs_dirent *dst, int depth )
|
|
{
|
|
char *buffer = page_alloc(1);
|
|
|
|
int length = fs_dirent_list(src, buffer, PAGE_SIZE);
|
|
if (length <= 0) goto failure;
|
|
|
|
char *name = buffer;
|
|
while (name && (name - buffer) < length) {
|
|
|
|
// Skip relative directory entries.
|
|
if (strcmp(name,".") == 0 || (strcmp(name, "..") == 0)) {
|
|
goto next_entry;
|
|
}
|
|
|
|
struct fs_dirent *new_src = fs_dirent_lookup(src, name);
|
|
if(!new_src) {
|
|
printf("couldn't lookup %s in directory!\n",name);
|
|
goto next_entry;
|
|
}
|
|
|
|
int i;
|
|
for(i=0;i<depth;i++) printf(">");
|
|
|
|
if(fs_dirent_isdir(new_src)) {
|
|
printf("%s (dir)\n", name);
|
|
struct fs_dirent *new_dst = fs_dirent_mkdir(dst,name);
|
|
if(!new_dst) {
|
|
printf("couldn't create %s!\n",name);
|
|
fs_dirent_close(new_src);
|
|
goto next_entry;
|
|
}
|
|
int res = fs_dirent_copy(new_src, new_dst,depth+1);
|
|
fs_dirent_close(new_dst);
|
|
if(res<0) goto failure;
|
|
} else {
|
|
printf("%s (%d bytes)\n", name,fs_dirent_size(new_src));
|
|
struct fs_dirent *new_dst = fs_dirent_mkfile(dst, name);
|
|
if(!new_dst) {
|
|
printf("couldn't create %s!\n",name);
|
|
fs_dirent_close(new_src);
|
|
goto next_entry;
|
|
}
|
|
|
|
char * filebuf = page_alloc(0);
|
|
if (!filebuf) {
|
|
fs_dirent_close(new_src);
|
|
fs_dirent_close(new_dst);
|
|
goto failure;
|
|
}
|
|
|
|
uint32_t file_size = fs_dirent_size(new_src);
|
|
uint32_t offset = 0;
|
|
|
|
while(offset<file_size) {
|
|
uint32_t chunk = MIN(PAGE_SIZE,file_size-offset);
|
|
fs_dirent_read(new_src, filebuf, chunk, offset );
|
|
fs_dirent_write(new_dst, filebuf, chunk, offset );
|
|
offset += chunk;
|
|
}
|
|
|
|
page_free(filebuf);
|
|
|
|
fs_dirent_close(new_dst);
|
|
}
|
|
|
|
fs_dirent_close(new_src);
|
|
|
|
next_entry:
|
|
name += strlen(name) + 1;
|
|
}
|
|
|
|
page_free(buffer);
|
|
return 0;
|
|
|
|
failure:
|
|
page_free(buffer);
|
|
return KERROR_NOT_FOUND;
|
|
}
|