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