__attribute__((noinline)) static void tm_process_exit(int code) { spinlock_acquire(¤t_thread->status_lock); if(code != -9) current_process->exit_reason.cause = __EXIT; current_process->exit_reason.ret = code; current_process->exit_reason.pid = current_process->pid; spinlock_release(¤t_thread->status_lock); /* update times */ if(current_process->parent) { time_t total_utime = current_process->utime + current_process->cutime; time_t total_stime = current_process->stime + current_process->cstime; atomic_fetch_add_explicit(¤t_process->parent->cutime, total_utime, memory_order_relaxed); atomic_fetch_add_explicit(¤t_process->parent->cstime, total_stime, memory_order_relaxed); } file_close_all(); if(current_process->root) vfs_icache_put(current_process->root); if(current_process->cwd) vfs_icache_put(current_process->cwd); mutex_destroy(¤t_process->fdlock); mm_destroy_all_mappings(current_process); linkedlist_destroy(&(current_process->mappings)); valloc_destroy(¤t_process->mmf_valloc); /* this is done before SIGCHILD is sent out */ atomic_fetch_or(¤t_process->flags, PROCESS_EXITED); if(current_process->parent) { struct process *init = tm_process_get(0); assert(init); __linkedlist_lock(process_list); struct process *child; struct linkedentry *node; for(node = linkedlist_iter_start(process_list); node != linkedlist_iter_end(process_list); node = linkedlist_iter_next(node)) { child = linkedentry_obj(node); if(child->parent == current_process) { tm_process_inc_reference(init); child->parent = init; tm_process_put(current_process); } } __linkedlist_unlock(process_list); tm_signal_send_process(current_process->parent, SIGCHILD); tm_blocklist_wakeall(¤t_process->waitlist); tm_process_put(init); } tm_process_put(current_process); /* fork starts us out at refs = 1 */ }
void vfs_inode_umount(struct inode *node) { assert(node->mount); node->mount->point = 0; node->mount = 0; vfs_icache_put(node); }
// Translate a mount point to the root node of the mounted filesystem. struct inode *fs_resolve_mount(struct inode *node) { struct inode *ret = node; if(node && node->mount) { ret = fs_read_root_inode(node->mount); vfs_icache_put(node); } return ret; }
int vfs_inode_chdir(struct inode *node) { if(!S_ISDIR(node->mode)) return -ENOTDIR; struct inode *old = current_process->cwd; current_process->cwd = node; vfs_inode_get(node); vfs_icache_put(old); return 0; }
int vfs_inode_chroot(struct inode *node) { if(!S_ISDIR(node->mode)) return -ENOTDIR; if(current_process->effective_uid) return -EPERM; struct inode *old = current_process->root; current_process->root = node; vfs_inode_get(node); vfs_icache_put(old); return 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; }
int sys_stat(char *f, struct stat *statbuf, bool lin) { if(!f || !statbuf) return -EINVAL; struct inode *i; int res; i = (struct inode *) (lin ? fs_path_resolve_inode(f, RESOLVE_NOLINK, &res) : fs_path_resolve_inode(f, 0, &res)); if(!i) return res; do_stat(i, statbuf); vfs_icache_put(i); return 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; }
/* 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 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; }
/* read in an inode from the inode cache, OR pull it in from the FS */ struct inode *vfs_icache_get(struct filesystem *fs, uint32_t num) { /* create if it doesn't exist */ struct inode *node; assert(fs); int newly_created = 0; uint32_t key[2] = {fs->id, num}; mutex_acquire(ic_lock); if((node = hash_lookup(icache, key, sizeof(key))) == NULL) { /* didn't find it. Okay, create one */ node = vfs_inode_create(); node->filesystem = fs; node->flags = INODE_NEEDREAD; node->id = num; node->key[0] = fs->id; node->key[1] = num; hash_insert(icache, node->key, sizeof(node->key), &node->hash_elem, node); newly_created = 1; } assert(node->filesystem == fs); atomic_fetch_add(&node->count, 1); /* move to in-use */ if(!(atomic_fetch_or(&node->flags, INODE_INUSE) & INODE_INUSE)) { atomic_fetch_add(&fs->usecount, 1); if(!newly_created) { queue_remove(ic_lru, &node->lru_item); linkedlist_insert(ic_inuse, &node->inuse_item, node); } } if(fs_inode_pull(node) != 0) { /* pull failed. Probably EIO. */ vfs_icache_put(node); node = 0; } mutex_release(ic_lock); return node; }
/* 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; }