/* this entry returns the inode pointed to by the given directory entry. It also * handles redirecting for symbolic links */ struct inode *fs_dirent_readinode(struct dirent *dir, bool perms) { assert(dir); if(perms) { if(!vfs_inode_check_permissions(dir->parent, MAY_EXEC, 0)) return 0; } return vfs_icache_get(dir->filesystem, dir->ino); }
ssize_t fs_inode_read(struct inode *node, size_t off, size_t count, unsigned char *buf) { if(!vfs_inode_check_permissions(node, MAY_READ, 0)) return -EACCES; ssize_t ret; ret = fs_callback_inode_read(node, off, count, buf); return ret; }
ssize_t fs_inode_write(struct inode *node, size_t off, size_t count, const unsigned char *buf) { if(S_ISDIR(node->mode)) return -EISDIR; if(!vfs_inode_check_permissions(node, MAY_WRITE, 0)) return -EACCES; ssize_t ret = fs_callback_inode_write(node, off, count, buf); if(ret > 0) { node->mtime = time_get_epoch(); vfs_inode_set_dirty(node); } return ret; }
int sys_getdents(int fd, struct dirent_posix *dirs, unsigned int count) { struct file *f = file_get(fd); if(!f) return -EBADF; unsigned nex; if(!vfs_inode_check_permissions(f->inode, MAY_READ, 0)) { file_put(f); return -EACCES; } rwlock_acquire(&f->inode->lock, RWL_READER); int r = fs_callback_inode_getdents(f->inode, f->pos, dirs, count, &nex); rwlock_release(&f->inode->lock, RWL_READER); f->pos = nex; file_put(f); return r; }
int fs_link(struct inode *dir, struct inode *target, const char *name, size_t namelen, bool allow_incomplete_directories) { if(!vfs_inode_check_permissions(dir, MAY_WRITE, 0)) return -EACCES; if(!S_ISDIR(dir->mode)) return -ENOTDIR; rwlock_acquire(&dir->lock, RWL_WRITER); if(S_ISDIR(dir->mode) && (dir->nlink == 1) && !allow_incomplete_directories) { rwlock_release(&dir->lock, RWL_WRITER); return -ENOSPC; } rwlock_acquire(&target->metalock, RWL_WRITER); int r = fs_callback_inode_link(dir, target, name, namelen); if(!r) atomic_fetch_add(&target->nlink, 1); rwlock_release(&target->metalock, RWL_WRITER); rwlock_release(&dir->lock, RWL_WRITER); return r; }
/* 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 function returns the directory entry associated with the name 'name' under * the inode 'node'. It must be careful to lookup the entry in the cache first. */ struct dirent *fs_dirent_lookup(struct inode *node, const char *name, size_t namelen) { if(!vfs_inode_check_permissions(node, MAY_READ, 0)) return 0; if(!S_ISDIR(node->mode)) return 0; if(node == current_process->root && !strncmp(name, "..", 2) && namelen == 2) return fs_dirent_lookup(node, ".", 1); mutex_acquire(dirent_cache_lock); rwlock_acquire(&node->lock, RWL_WRITER); struct dirent *dir = vfs_inode_get_dirent(node, name, namelen); if(!dir) { dir = vfs_dirent_create(node); dir->count = 1; strncpy(dir->name, name, namelen); dir->namelen = namelen; int r = fs_callback_inode_lookup(node, name, namelen, dir); if(r) { dir->count = 0; vfs_dirent_destroy(dir); rwlock_release(&node->lock, RWL_WRITER); mutex_release(dirent_cache_lock); return 0; } vfs_inode_get(node); vfs_inode_add_dirent(node, dir); } else { if(atomic_fetch_add(&dir->count, 1) == 0) { fs_dirent_remove_lru(dir); vfs_inode_get(node); } } rwlock_release(&node->lock, RWL_WRITER); mutex_release(dirent_cache_lock); return dir; }
int do_exec(char *path, char **argv, char **env, int shebanged /* oh my */) { unsigned int i=0; addr_t end, eip; unsigned int argc=0, envc=0; char **backup_argv=0, **backup_env=0; /* Sanity */ if(!path || !*path) return -EINVAL; /* Load the file, and make sure that it is valid and accessible */ if(EXEC_LOG == 2) printk(0, "[%d]: Checking executable file (%s)\n", current_process->pid, path); struct file *efil; int err_open; efil = fs_file_open(path, _FREAD, 0, &err_open); if(!efil) return err_open; /* are we allowed to execute it? */ if(!vfs_inode_check_permissions(efil->inode, MAY_EXEC, 0)) { file_put(efil); return -EACCES; } /* is it a valid elf? */ int header_size = 0; #if CONFIG_ARCH == TYPE_ARCH_X86_64 header_size = sizeof(elf64_header_t); #elif CONFIG_ARCH == TYPE_ARCH_X86 header_size = sizeof(elf32_header_t); #endif /* read in the ELF header, and check if it's a shebang */ if(header_size < 2) header_size = 2; unsigned char mem[header_size]; fs_file_pread(efil, 0, mem, header_size); if(__is_shebang(mem)) return loader_do_shebang(efil, argv, env); int other_bitsize=0; if(!is_valid_elf(mem, 2) && !other_bitsize) { file_put(efil); return -ENOEXEC; } if(EXEC_LOG == 2) printk(0, "[%d]: Copy data\n", current_process->pid); /* okay, lets back up argv and env so that we can * clear out the address space and not lose data... * If this call if coming from a shebang, then we don't check the pointers, * since they won't be from userspace */ size_t total_args_len = 0; if((shebanged || mm_is_valid_user_pointer(SYS_EXECVE, argv, 0)) && argv) { while((shebanged || mm_is_valid_user_pointer(SYS_EXECVE, argv[argc], 0)) && argv[argc] && *argv[argc]) argc++; backup_argv = (char **)kmalloc(sizeof(addr_t) * argc); for(i=0;i<argc;i++) { backup_argv[i] = (char *)kmalloc(strlen(argv[i]) + 1); _strcpy(backup_argv[i], argv[i]); total_args_len += strlen(argv[i])+1 + sizeof(char *); } } if((shebanged || mm_is_valid_user_pointer(SYS_EXECVE, env, 0)) && env) { while((shebanged || mm_is_valid_user_pointer(SYS_EXECVE, env[envc], 0)) && env[envc] && *env[envc]) envc++; backup_env = (char **)kmalloc(sizeof(addr_t) * envc); for(i=0;i<envc;i++) { backup_env[i] = (char *)kmalloc(strlen(env[i]) + 1); _strcpy(backup_env[i], env[i]); total_args_len += strlen(env[i])+1 + sizeof(char *); } } total_args_len += 2 * sizeof(char *); /* and the path too! */ char *path_backup = (char *)kmalloc(strlen(path) + 1); _strcpy((char *)path_backup, path); path = path_backup; /* Preexec - This is the point of no return. Here we close out unneeded * file descs, free up the page directory and clear up the resources * of the task */ if(EXEC_LOG) printk(0, "Executing (p%dt%d, cpu %d, tty %d): %s\n", current_process->pid, current_thread->tid, current_thread->cpu->knum, current_process->pty ? current_process->pty->num : 0, path); preexec(); /* load in the new image */ strncpy((char *)current_process->command, path, 128); if(!loader_parse_elf_executable(mem, efil, &eip, &end)) eip=0; /* do setuid and setgid */ if(efil->inode->mode & S_ISUID) { current_process->effective_uid = efil->inode->uid; } if(efil->inode->mode & S_ISGID) { current_process->effective_gid = efil->inode->gid; } /* we don't need the file anymore, close it out */ file_put(efil); file_close_cloexec(); if(!eip) { printk(5, "[exec]: Tried to execute an invalid ELF file!\n"); free_dp(backup_argv, argc); free_dp(backup_env, envc); kfree(path); tm_thread_exit(0); } if(EXEC_LOG == 2) printk(0, "[%d]: Updating task values\n", current_process->pid); /* Setup the task with the proper values (libc malloc stack) */ addr_t end_l = end; end = ((end-1)&PAGE_MASK) + PAGE_SIZE; total_args_len += PAGE_SIZE; /* now we need to copy back the args and env into userspace * writeable memory...yippie. */ addr_t args_start = end + PAGE_SIZE; addr_t env_start = args_start; addr_t alen = 0; mm_mmap(end, total_args_len, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, 0, 0, 0); if(backup_argv) { memcpy((void *)args_start, backup_argv, sizeof(addr_t) * argc); alen += sizeof(addr_t) * argc; *(addr_t *)(args_start + alen) = 0; /* set last argument value to zero */ alen += sizeof(addr_t); argv = (char **)args_start; for(i=0;i<argc;i++) { char *old = argv[i]; char *new = (char *)(args_start+alen); unsigned len = strlen(old) + 4; argv[i] = new; _strcpy(new, old); kfree(old); alen += len; } kfree(backup_argv); } env_start = args_start + alen; alen = 0; if(backup_env) { memcpy((void *)env_start, backup_env, sizeof(addr_t) * envc); alen += sizeof(addr_t) * envc; *(addr_t *)(env_start + alen) = 0; /* set last argument value to zero */ alen += sizeof(addr_t); env = (char **)env_start; for(i=0;i<envc;i++) { char *old = env[i]; char *new = (char *)(env_start+alen); unsigned len = strlen(old) + 1; env[i] = new; _strcpy(new, old); kfree(old); alen += len; } kfree(backup_env); } end = (env_start + alen) & PAGE_MASK; current_process->env = env; current_process->argv = argv; kfree(path); /* set the heap locations, and map in the start */ current_process->heap_start = current_process->heap_end = end + PAGE_SIZE*2; addr_t ret = mm_mmap(end + PAGE_SIZE, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, 0, 0, 0); /* now, we just need to deal with the syscall return stuff. When the syscall * returns, it'll just jump into the entry point of the new process */ tm_thread_lower_flag(current_thread, THREAD_SCHEDULE); /* the kernel cares if it has executed something or not */ if(!(kernel_state_flags & KSF_HAVEEXECED)) set_ksf(KSF_HAVEEXECED); arch_loader_exec_initializer(argc, eip); if(EXEC_LOG == 2) printk(0, "[%d]: Performing call\n", current_process->pid); 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; }