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