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;
 | |
| }
 |