aboutsummaryrefslogblamecommitdiff
path: root/src/kernel/user/vfs.c
blob: 6df0a2595a2893b9e830b8e8198a8cb7fef27378 (plain) (tree)
1
2
3
4
5
6
7
8







                                   




                             









                                                                   
                                                                          







                                        
                                        

                                        
                                                                            

                                               



                                                         
 
                     

                                    



                               
 








                                                                                                          
         





                                           

 















                                                           


                     

                                                                                  








                            



                                                    
                                                                                     
                 
                                                                   



                         



                                                            
                             
                  
                               


                                  
                             

                           


                                                  
 

                                               
 

                                                                     
                                                                
                                                       
 

                                                 
 







                                                                        
                                                                
                 
                                           
                        

                                       



                                                           

                                



                                                                              
                                                  





                                             
                                                             
                               


                         
                                 
                         



                                     
                                                                    


                                  
                                                                                 
                                                    
         



                                                         

                                  





                               
                                        


                                                  
                 


                                                            

                                 



                              





                                                                  
                                                  
                             
























                                                                                             


                                                                                                   










































                                                                              

 

                                 
 
                                                      

                                                       
                           
                                                                      

                                 
                             
                                                                   

                               



                         
                                            

                                                       

                           
                                                                      

                                 




                                                                           
                             
                                                             

                               

                         

 
                                                          

                                                     
                               
                                                                                   
                                          

                               
                                                                                 




                                          







                                                                              
 











                                                                                        
                                                                                        





















                                                                                              


                                  

 
                                                      
                                                    

                                 
                             
                                                       

                               

                         

 




                                                            

                                           
                                                    

                                                      
                                                         

                 


                             
 

                           
                                                                   
                               
 
                                             

                                 

                    
                    
                       










                                                                    





                                  
                                    

                              

                                                                         
                                                 



                           



                                   
                                                                        
                                           
 
                                              
 
                                                       


                                                                               
                                            
 
                                               
 
                                                        

 
                                            
                                                                     

 


                                                         
                                                
 
                                                     

 
                                                               

                                              
                                                                            

 
                                                    
                                  



                                                    
                                                   

 
                                   
#include <debug.h>
#include <vfs.h>
#include <string.h>

// ============================= //
// FILESYSTEM DRIVER REGISTERING //
// ============================= //

static fs_ops_t no_fs_ops = {
	.shutdown = 0,
	.add_source = 0,
};

typedef struct fs_driver {
	const char* name;
	fs_driver_ops_t *ops;
	struct fs_driver *next;
} fs_driver_t;

fs_driver_t *drivers = 0;

void register_fs_driver(const char* name, fs_driver_ops_t *ops) {
	fs_driver_t *d = (fs_driver_t*)malloc(sizeof(fs_driver_t));
	ASSERT(d != 0); // should we fail in a more graceful manner ? TODO

	d->name = name;
	d->ops = ops;
	d->next = drivers;
	drivers = d;
}

// ================================== //
// CREATING AND DELETING FILE SYSTEMS //
// ================================== //

fs_t *make_fs(const char* drv_name, fs_handle_t *source, const char* opts) {
	// Open file system
	fs_t *fs = (fs_t*)malloc(sizeof(fs_t));
	if (fs == 0) goto fail;

	fs->root = (fs_node_t*)malloc(sizeof(fs_node_t));
	if (fs->root == 0) goto fail;

	fs->refs = 1;
	fs->from_fs = 0;
	fs->ok_modes = FM_ALL_MODES;
	fs->root->refs = 1;
	fs->root->fs = fs;
	fs->root->parent = 0;
	fs->root->children = 0;

	// Look for driver
	for(fs_driver_t *i = drivers; i != 0; i = i->next) {
		if ((drv_name != 0 && strcmp(i->name, drv_name) == 0) || (drv_name == 0 && source != 0)) {
			if (i->ops->make(source, opts, fs)) {
				return fs;
			} else {
				goto fail;
			}
		}
	}


fail:
	if (fs && fs->root) free(fs->root);
	if (fs) free(fs);
	return 0;
}

fs_t *fs_subfs(fs_t *fs, const char* root, int ok_modes) {
	fs_node_t* new_root = fs_walk_path(fs->root, root);
	if (new_root == 0) return 0;

	fs_t *subfs = (fs_t*)malloc(sizeof(fs_t));
	if (subfs == 0) return 0;

	subfs->refs = 1;
	subfs->from_fs = fs;
	subfs->ok_modes = ok_modes & fs->ok_modes;

	subfs->ops = &no_fs_ops;
	subfs->data = 0;

	subfs->root = new_root;

	return subfs;
}

bool fs_add_source(fs_t *fs, fs_handle_t *source, const char* opts) {
	return fs->ops->add_source && fs->ops->add_source(fs->data, source, opts);
}

void ref_fs(fs_t *fs) {
	fs->refs++;
}

void unref_fs(fs_t *fs) {
	fs->refs--;
	if (fs->refs == 0) {
		if (fs->from_fs != 0) {
			unref_fs_node(fs->root);
		} else {
			ASSERT(fs->root->refs == 1);
			if (fs->root->ops->dispose) fs->root->ops->dispose(fs->root);
		}
		if (fs->ops->shutdown) fs->ops->shutdown(fs->data);
		free(fs);
	}
}

// ====================================================== //
// WALKING IN THE FILE SYSTEM CREATING AND DELETING NODES //

void ref_fs_node(fs_node_t *n) {
	mutex_lock(&n->lock);
	n->refs++;
	mutex_unlock(&n->lock);
}

void unref_fs_node(fs_node_t *n) {
	mutex_lock(&n->lock);
	n->refs--;
	if (n->refs == 0) {
		// delete object!
		if (n->fs) {
			ASSERT (n != n->fs->root);

			ASSERT(n->parent != 0);
			ASSERT(n->name != 0);

			mutex_lock(&n->parent->lock);
			hashtbl_remove(n->parent->children, n->name);
			if (n->ops->dispose) n->ops->dispose(n);
			mutex_unlock(&n->parent->lock);

			unref_fs_node(n->parent);
			unref_fs(n->fs);

			if (n->children != 0) {
				ASSERT(hashtbl_count(n->children) == 0);
				delete_hashtbl(n->children);
			}
		} else {
			// stand-alone IPC/SHM object
			ASSERT(n->parent == 0 && n->children == 0);

			if (n->ops->dispose) n->ops->dispose(n);
		}
		if (n->name) free(n->name);
		free(n);
	} else {
		mutex_unlock(&n->lock);
	}
}

fs_node_t* fs_walk_one(fs_node_t* from, const char* file) {
	mutex_lock(&from->lock);

	if (from->children != 0) {
		fs_node_t *n = (fs_node_t*)hashtbl_find(from->children, file);
		if (n != 0) {
			ref_fs_node(n);
			mutex_unlock(&from->lock);
			return n;
		}
	}

	bool walk_ok = false, add_ok = false;

	fs_node_t *n = (fs_node_t*)malloc(sizeof(fs_node_t));
	if (n == 0) goto error;

	n->fs = from->fs;
	n->refs = 1;
	n->lock = MUTEX_UNLOCKED;
	n->parent = from;
	n->children = 0;
	n->name = strdup(file);
	if (n->name == 0) goto error;
	
	walk_ok = from->ops->walk && from->ops->walk(from, file, n);
	if (!walk_ok) goto error;

	if (from->children == 0) {
		from->children = create_hashtbl(str_key_eq_fun, str_hash_fun, 0);
		if (from->children == 0) goto error;
	}

	add_ok = hashtbl_add(from->children, n->name, n);
	if (!add_ok) goto error;

	mutex_unlock(&from->lock);

	ref_fs_node(n->parent);
	ref_fs(n->fs);

	return n;

error:
	if (walk_ok) n->ops->dispose(n);
	if (n != 0 && n->name != 0) free(n->name);
	if (n != 0) free(n);
	mutex_unlock(&from->lock);
	return 0;
}

fs_node_t* fs_walk_path(fs_node_t* from, const char* path) {
	if (*path == '/') path++;

	fs_node_t *n = from;
	ref_fs_node(n);

	while (n && (*path)) {
		if (*path == '/') {
			// we don't want multiple slashes, so fail
			unref_fs_node(n);
			return 0;
		}

		const char* d = strchr(path, '/');
		if (d == 0) {
			fs_node_t *n2 = fs_walk_one(n, path);
			unref_fs_node(n);

			return n2;
		} else {
			size_t nlen = d - path;
			if (nlen >= DIR_MAX - 1) {
				unref_fs_node(n);
				return 0;	// sorry, path item too long
			}
			char name_buf[DIR_MAX];
			strncpy(name_buf, path, nlen);
			name_buf[nlen] = 0;

			fs_node_t *n2 = fs_walk_one(n, name_buf);
			unref_fs_node(n);
			n = n2;

			path = d + 1;
		}
	}
	return n;
}

fs_node_t* fs_walk_path_except_last(fs_node_t* from, const char* path, char* last_file_buf) {
	// This function does NOT walk into the last component of the path (it may not even exist),
	// instead it isolates it in the buffer last_file
	// This buffer is expected to be of size DIR_MAX (defined in fs.h)
	if (*path == '/') path++;
	if (*path == 0) return 0;		// no last component !

	fs_node_t *n = from;
	ref_fs_node(n);

	while (n && (*path)) {
		if (*path == '/') {
			// we don't want multiple slashes, so fail
			unref_fs_node(n);
			return 0;
		}

		const char* d = strchr(path, '/');
		if (d == 0) {
			if (strlen(path) >= DIR_MAX - 1) {
				unref_fs_node(n);
				return 0;	// sorry, path item too long
			}
			strcpy(last_file_buf, path);
			return n;
		} else {
			size_t nlen = d - path;
			if (nlen >= DIR_MAX - 1) {
				unref_fs_node(n);
				return 0;	// sorry, path item too long
			}
			strncpy(last_file_buf, path, nlen);
			last_file_buf[nlen] = 0;

			if (*(d+1) == 0) {
				// trailing slash !
				return n;
			} else {
				fs_node_t *n2 = fs_walk_one(n, last_file_buf);
				unref_fs_node(n);
				n = n2;

				path = d + 1;
			}
		}
	}
	return n;
}

// =========================== //
// DOING THINGS IN FILESYSTEMS //

bool fs_create(fs_t *fs, const char* file, int type) {
	if (!(fs->ok_modes & FM_DCREATE)) return false;

	char name[DIR_MAX];
	fs_node_t *n = fs_walk_path_except_last(fs->root, file, name);
	if (n == 0) return false;

	mutex_lock(&n->lock);
	bool ret = n->ops->create && n->ops->create(n, name, type);
	mutex_unlock(&n->lock);

	unref_fs_node(n);
	return ret;
}

bool fs_delete(fs_t *fs, const char* file) {
	if (!(fs->ok_modes & FM_DDELETE)) return false;

	char name[DIR_MAX];

	fs_node_t* n = fs_walk_path_except_last(fs->root, file, name);
	if (n == 0) return false;

	if (n->children != 0) {
		fs_node_t* x = (fs_node_t*)hashtbl_find(n->children, name);
		if (x != 0) return false;
	}

	mutex_lock(&n->lock);
	bool ret = n->ops->delete && n->ops->delete(n, name);
	mutex_unlock(&n->lock);

	unref_fs_node(n);
	return ret;
}

bool fs_move(fs_t *fs, const char* from, const char* to) {
	if (!(fs->ok_modes & FM_DMOVE)) return false;

	char old_name[DIR_MAX];
	fs_node_t *old_parent = fs_walk_path_except_last(fs->root, from, old_name);
	if (old_parent == 0) return false;

	char new_name[DIR_MAX];
	fs_node_t *new_parent = fs_walk_path_except_last(fs->root, to, new_name);
	if (new_parent == 0) {
		unref_fs_node(old_parent);
		return false;
	}

	bool ret = false;
	if (!old_parent->ops->move) goto end;

	mutex_lock(&old_parent->lock);
	mutex_lock(&new_parent->lock);

	fs_node_t *the_node = (old_parent->children != 0 ?
		(fs_node_t*)hashtbl_find(old_parent->children, old_name) : 0);

	if (the_node) {
		mutex_lock(&the_node->lock);

		char* new_name_dup = strdup(new_name);
		if (new_name_dup == 0) goto unlock_end;	// we failed

		bool add_ok = hashtbl_add(new_parent->children, new_name_dup, the_node);
		if (!add_ok) {
			free(new_name_dup);
			goto unlock_end;
		}
			
		ret = old_parent->ops->move(old_parent, old_name, new_parent, new_name);

		if (ret) {
			// adjust node parameters
			hashtbl_remove(old_parent->children, old_name);
			free(the_node->name);
			the_node->name = new_name_dup;
			the_node->parent = new_parent;
		} else {
			hashtbl_remove(new_parent->children, new_name_dup);
			free(new_name_dup);
		}

	unlock_end:
		mutex_unlock(&the_node->lock);
	} else {
		ret = old_parent->ops->move(old_parent->data, old_name, new_parent, new_name);
	}

	mutex_unlock(&old_parent->lock);
	mutex_unlock(&new_parent->lock);

end:
	unref_fs_node(old_parent);
	unref_fs_node(new_parent);
	return ret;
}

bool fs_stat(fs_t *fs, const char* file, stat_t *st) {
	fs_node_t* n = fs_walk_path(fs->root, file);
	if (n == 0) return false;

	mutex_lock(&n->lock);
	bool ret = n->ops->stat && n->ops->stat(n, st);
	mutex_unlock(&n->lock);

	unref_fs_node(n);
	return ret;
}

// =================== //
// OPERATIONS ON FILES //
// =================== //

fs_handle_t* fs_open(fs_t *fs, const char* file, int mode) {
	if (mode & ~fs->ok_modes) return 0;

	fs_node_t *n = fs_walk_path(fs->root, file);
	if (n == 0 && (mode & FM_CREATE)) {
		if (fs_create(fs, file, FT_REGULAR)) {
			n = fs_walk_path(fs->root, file);
		}
	}
	if (n == 0) return 0;

	mutex_lock(&n->lock);

	mode &= ~FM_CREATE;

	fs_handle_t *h = (fs_handle_t*)malloc(sizeof(fs_handle_t));
	if (h == 0) goto error;

	bool open_ok = n->ops->open(n, mode);
	if (!open_ok) goto error;

	h->refs = 1;
	h->fs = fs;
	h->node = n;
	h->mode = mode;

	// our reference to node n is transferred to the file handle
	mutex_unlock(&n->lock);
	ref_fs(fs);
	return h;

error:
	mutex_unlock(&n->lock);
	unref_fs_node(n);
	if (h != 0) free(h);
	return 0;
}

void ref_file(fs_handle_t *file) {
	file->refs++;
}

void unref_file(fs_handle_t *file) {
	file->refs--;
	if (file->refs == 0) {
		if (file->node->ops->close) file->node->ops->close(file);
		unref_fs_node(file->node);
		if (file->fs) unref_fs(file->fs);
		free(file);
	}
}

int file_get_mode(fs_handle_t *f) {
	return f->mode;
}

size_t file_read(fs_handle_t *f, size_t offset, size_t len, char* buf) {
	if (!(f->mode & FM_READ)) return 0;

	if (f->node->ops->read == 0) return 0;

	return f->node->ops->read(f, offset, len, buf);
}

size_t file_write(fs_handle_t *f, size_t offset, size_t len, const char* buf) {
	if (!(f->mode & FM_WRITE)) return 0;

	if (f->node->ops->write == 0) return 0;

	return f->node->ops->write(f, offset, len, buf);
}

bool file_stat(fs_handle_t *f, stat_t *st) {
	return f->node->ops->stat && f->node->ops->stat(f->node, st);
}

int file_ioctl(fs_handle_t *f, int command, void* data) {
	if (!(f->mode & FM_IOCTL)) return -1;

	if (f->node->ops->ioctl == 0) return -1;

	return f->node->ops->ioctl(f, command, data);
}

bool file_readdir(fs_handle_t *f, size_t ent_no, dirent_t *d) {
	if (!(f->mode & FM_READDIR)) return 0;

	return f->node->ops->readdir && f->node->ops->readdir(f, ent_no, d);
}

int file_poll(fs_handle_t *f, void** out_wait_obj) {
	if (!f->node->ops->poll) {
		if (out_wait_obj) *out_wait_obj = 0;
		return 0;
	}

	return f->node->ops->poll(f, out_wait_obj);
}

/* vim: set ts=4 sw=4 tw=0 noet :*/