Beispiel #1
0
/* This is the main workhorse for the virtual filesystem. Given a start point and
 * a path string, it resolves the path into a directory entry. This is simply a
 * matter of looping through the path (seperated at '/', of course), and reading
 * the successive directory entries and inodes (see vfs_dirent_lookup and
 * vfs_dirent_readinode). The one thing it needs to pay attention to is the case
 * of traversing backwards up through a mount point. */
struct dirent *fs_do_path_resolve(struct inode *start, const char *path, int sym_start_level, int *result)
{
	vfs_inode_get(start);
	ASSERT(start);
	if(!*path)
		path = ".";
	if(result) *result = 0;
	
	struct inode *node = start;
	struct dirent *dir = 0;
	struct inode *nextnode = 0;
	while(node && *path) {
		if(dir) {
			vfs_dirent_release(dir);
			dir = 0;
		}
		char *delim = strchr(path, '/');
		if(delim != path) {
			nextnode = 0;
			const char *name = path;
			size_t namelen = delim ? (size_t)(delim - name) : strlen(name);
			dir = fs_dirent_lookup(node, name, namelen);
			if(!dir) {
				if(result) *result = -ENOENT;
				vfs_icache_put(node);
				return 0;
			}
			if(delim) {
				nextnode = fs_dirent_readinode(dir, true);
				if(namelen == 2 && !strncmp("..", name, 2)
						&& node->id == node->filesystem->root_inode_id) {
					// Traverse back up through a mount.
					vfs_inode_get(nextnode->filesystem->point);
					struct inode *tmp = nextnode;
					nextnode = nextnode->filesystem->point;
					vfs_icache_put(tmp);
				} else if(nextnode) {
					if((*result = fs_resolve_iter_symlink(&dir, &nextnode, sym_start_level))) {
						vfs_icache_put(node);
						return 0;
					}
					nextnode = fs_resolve_mount(nextnode);
					if(!nextnode) {
						vfs_icache_put(node);
						return 0;
					}
				}
			}
			vfs_icache_put(node);
			node = nextnode;
		}
		path = delim + 1;
	}
	if(nextnode)
		vfs_icache_put(nextnode);
	return dir;
}
Beispiel #2
0
/* This one does the extra step of getting the inode pointed to by the dirent.
 * Since most fs functions just want this inode and don't care about the directory
 * entry that was used to point to it, this is used quite frequently. */
struct inode *fs_path_resolve_inode(const char *path, int flags, int *error)
{
	struct dirent *dir = fs_path_resolve(path, 0, error);
	if(!dir)
		return 0;
	struct inode *node = fs_dirent_readinode(dir, true);
	if(!node) {
		*error = -EIO;
		vfs_dirent_release(dir);
		return 0;
	}
	if(!(flags & RESOLVE_NOLINK)) {
		if((*error = fs_resolve_iter_symlink(&dir, &node, 0)))
			return 0;
	}
	if(!(flags & RESOLVE_NOMOUNT))
		node = fs_resolve_mount(node);
	vfs_dirent_release(dir);
	return node;
}
Beispiel #3
0
/* this function is called when a reference to a dirent is released. There is one
 * special thing to note: according to POSIX, an unlinked dirent is only actually
 * deleted when the last reference to it is dropped. Because of that, unlink just
 * sets a flag which tells this function to do the actual deletion. In either case,
 * this function doesn't deallocate anything, it just moves it to an LRU queue. */
int vfs_dirent_release(struct dirent *dir)
{
	int r = 0;
	struct inode *parent = dir->parent;
	rwlock_acquire(&parent->lock, RWL_WRITER);
	if(atomic_fetch_sub(&dir->count, 1) == 1) {
		if(dir->flags & DIRENT_UNLINK) {
			struct inode *target = fs_dirent_readinode(dir, false);
			/* Well, sadly, target being null is a pretty bad thing,
			 * but we can't panic, because the filesystem could be
			 * set up to actually act like this... :( */
			vfs_inode_del_dirent(parent, dir);
			if(!target) {
				printk(KERN_ERROR, "belated unlink failed to read target inode: %s - %d\n",
						dir->name, dir->ino);
			} else {
				r = fs_callback_inode_unlink(parent, dir->name, dir->namelen, target);
				if(!r) {
					assert(target->nlink > 0);
					atomic_fetch_sub(&target->nlink, 1);
					if(!target->nlink && (target->flags & INODE_DIRTY))
						vfs_inode_unset_dirty(target);
				}
				vfs_icache_put(target);
			}
			vfs_dirent_destroy(dir);
		} else {
			/* add to LRU */
			queue_enqueue_item(dirent_lru, &dir->lru_item, dir);
		}
		rwlock_release(&parent->lock, RWL_WRITER);
		/* So, here's the thing. Technically, we still have a pointer that points
		 * to parent: in dir->parent. We just have to make sure that each time
		 * we use this pointer, we don't screw up */
		vfs_icache_put(parent); /* for the dir->parent pointer */
	} else
		rwlock_release(&parent->lock, RWL_WRITER);

	return r;
}
Beispiel #4
0
int fs_resolve_iter_symlink(struct dirent **dir, struct inode **node, int start_level)
{
	struct inode *oldnode = *node;
	struct dirent *olddir = *dir;
	struct dirent *newdir = 0;
	struct inode *newnode = 0;
	int err;
	int level = start_level;

	while(level++ < 32) {
		newdir = fs_resolve_symlink(olddir, oldnode, level, &err);
		if(S_ISLNK(oldnode->mode)) {
			vfs_icache_put(oldnode);
			vfs_dirent_release(olddir);
			if(!newdir)
				return err;
			newnode = fs_dirent_readinode(newdir, true);
			if(!newnode) {
				vfs_dirent_release(newdir);
				return -EIO;
			}
			olddir = newdir;
			oldnode = newnode;
		} else {
			break;
		}
	}
	if(level >= 32) {
		vfs_icache_put(newnode);
		vfs_dirent_release(newdir);
		return -ELOOP;
	}
	if(newnode) {
		*dir = newdir;
		*node = newnode;
	}
	return 0;
}
Beispiel #5
0
/* note that this function doesn't actually delete the dirent. It just sets a flag that
 * says "hey, this was deleted". See vfs_dirent_release for more details. An important
 * aspect is that on unlinking a directory, it does unlink the . and .. entries, even
 * though the directory won't actually be deleted until vfs_dirent_release gets called
 * and the last reference is released. */
static int do_fs_unlink(struct inode *node, const char *name, size_t namelen, int rec)
{
	if(!vfs_inode_check_permissions(node, MAY_WRITE, 0))
		return -EACCES;
	struct dirent *dir = fs_dirent_lookup(node, name, namelen);
	if(!dir)
		return -ENOENT;
	struct inode *target = fs_dirent_readinode(dir, true);
	if(!target || (rec && S_ISDIR(target->mode) && !fs_inode_dirempty(target))) {
		if(target)
			vfs_icache_put(target);
		vfs_dirent_release(dir);
		return -ENOTEMPTY;
	}
	atomic_fetch_or_explicit(&dir->flags, DIRENT_UNLINK, memory_order_release);
	if(S_ISDIR(target->mode) && rec) {
		do_fs_unlink(target, "..", 2, 0);
		do_fs_unlink(target, ".", 1, 0);
	}
	vfs_icache_put(target);
	vfs_dirent_release(dir);
	return 0;
}
Beispiel #6
0
/* This is possibly the lengthiest function in the VFS (possibly in the entire kernel!).
 * It resolves a path until the last name in the path string, and then tries to create
 * a new file with that name. It requires a lot of in-sequence checks for permission,
 * existance, is-a-directory, and so forth. */
struct inode *fs_path_resolve_create_get(const char *path, int flags, mode_t mode, int *result, struct dirent **dirent)
{
	// Step 1: Split the path up into directory to create in, and name of new file.
	int len = strlen(path) + 1;
	char tmp[len];
	memcpy(tmp, path, len);
	char *del = strrchr(tmp, '/');
	if(del)
		*del = 0;
	char *dirpath = del ? tmp : ".";
	char *name = del ? del + 1 : tmp;
	if(dirpath[0] == 0)
		dirpath = "/";
	if(dirent)
		*dirent = 0;
	if(result)
		*result = 0;

	// Step 2: Resolve the target directory.
	struct inode *dir = fs_path_resolve_inode(dirpath, flags, result);
	if(!dir)
		return 0;
	if(!S_ISDIR(dir->mode)) {
		if(result) *result = -ENOTDIR;
		return 0;
	}

	// Step 3: Try to look up the file that we're trying to create.
	struct dirent *test = fs_dirent_lookup(dir, name, strlen(name));
	if(test) {
		// If it was found, return it and its inode.
		if(dirent)
			*dirent = test;
		struct inode *ret = fs_dirent_readinode(test, true);
		if(ret)	
			ret = fs_resolve_mount(ret);
		if(!ret) {
			if(dirent)
				*dirent = 0;
			if(result)
				*result = -EIO;
			vfs_dirent_release(test);
		} else {
			if(!dirent)
				vfs_dirent_release(test);
			if(result)
				*result = 0;
		}
		vfs_icache_put(dir);
		return ret;
	}
	
	// Didn't find the entry. Step 4: Create one.
	if(!vfs_inode_check_permissions(dir, MAY_WRITE, 0)) {
		if(result) *result = -EACCES;
		vfs_icache_put(dir);
		return 0;
	}
	if(dir->nlink == 1) {
		if(result) *result = -ENOSPC;
		vfs_icache_put(dir);
		return 0;
	}

	uint32_t id;
	// Step 4a: Allocate an inode.
	int r = fs_callback_fs_alloc_inode(dir->filesystem, &id);
	if(r) {
		if(result) *result = r;
		vfs_icache_put(dir);
		return 0;
	}

	// Step 4b: Read in that inode, and set some initial values (like creation time).
	struct inode *node = vfs_icache_get(dir->filesystem, id);
	if(!node) {
		vfs_icache_put(dir);
		if(result) *result = -EIO;
		return 0;
	}
	node->mode = mode;
	node->length = 0;
	node->ctime = node->mtime = time_get_epoch();
	vfs_inode_set_dirty(node);
	
	// If we're making a directory, create the . and .. entries.
	if(S_ISDIR(mode)) {
		// Create . and ..
		if(fs_link(node, node, ".", 1, true))
			r = -EPERM;
		if(fs_link(node, dir, "..", 2, true))
			r = -EMLINK;
	}

	// Step 4c: Create the link for the directory entry to the inode.
	r = fs_link(dir, node, name, strlen(name), false);

	if(result)
		*result = !r ? 1 : r;
	if(dirent && !r) {
		*dirent = fs_dirent_lookup(dir, name, strlen(name));
		if(!*dirent && node) {
			vfs_icache_put(node);
			vfs_icache_put(dir);
			if(result)
				*result = -EIO;
			return 0;
		}
	}
	vfs_icache_put(dir);
	return r ? 0 : node;
}