// SPDX-License-Identifier: ISC #include #include #include #include #include #include #include #include #include #include #include constexpr size_t PROCESS_INITIAL_FDS = 64; static struct process kernel_process; static atomic_bool kernel_process_ready; static atomic_int next_pid = 1; static size_t fd_words(size_t capacity) { return capacity / 64; } static void fd_table_destroy(struct fd_table* t) { mem_free(t->files); mem_free(t->open); mem_free(t->cloexec); *t = (struct fd_table){}; } static errno_t fd_table_resize(struct fd_table* t, size_t capacity) { struct vfs_file** files = mem_alloc(capacity * sizeof(*files), 0); uint64_t* open = mem_alloc(fd_words(capacity) * sizeof(*open), 0); uint64_t* cloexec = mem_alloc(fd_words(capacity) * sizeof(*cloexec), 0); if (!files || !open || !cloexec) { mem_free(files); mem_free(open); mem_free(cloexec); return ENOMEM; } if (t->capacity) { memcpy(files, t->files, t->capacity * sizeof(*files)); memcpy(open, t->open, fd_words(t->capacity) * sizeof(*open)); memcpy(cloexec, t->cloexec, fd_words(t->capacity) * sizeof(*cloexec)); } mem_free(t->files); mem_free(t->open); mem_free(t->cloexec); t->files = files; t->open = open; t->cloexec = cloexec; t->capacity = capacity; return 0; } static errno_t fd_table_grow(struct fd_table* t, size_t fd) { if (fd < t->capacity) return 0; size_t capacity = MAX(PROCESS_INITIAL_FDS, t->capacity); while (capacity <= fd) capacity *= 2; return fd_table_resize(t, MIN(capacity, PROCESS_MAX_FDS)); } static size_t fd_find_free(struct fd_table* t, size_t start) { for (size_t w = start / 64; w < fd_words(t->capacity); w++) { uint64_t used = t->open[w]; if (w == start / 64) used |= (1ull << (start % 64)) - 1; if (~used) return w * 64 + __builtin_ctzll(~used); } return MAX(start, t->capacity); } static void fd_install(struct fd_table* t, size_t fd, struct vfs_file* file, bool cloexec) { const uint64_t bit = 1ull << (fd % 64); t->files[fd] = file; t->open[fd / 64] |= bit; if (cloexec) t->cloexec[fd / 64] |= bit; else t->cloexec[fd / 64] &= ~bit; } static void fd_clear(struct fd_table* t, size_t fd) { const uint64_t bit = 1ull << (fd % 64); t->files[fd] = nullptr; t->open[fd / 64] &= ~bit; t->cloexec[fd / 64] &= ~bit; if (fd < t->next_free) t->next_free = fd; } static struct vfs_file* fd_lookup(struct process* proc, int fd) { if (fd < 0 || (size_t)fd >= proc->files.capacity) return nullptr; return proc->files.files[fd]; } static void process_init_common(struct process* proc) { refcount_init(&proc->rc); spin_init(&proc->tasks_lock); tailq_init(&proc->tasks); mutex_init(&proc->lock); mutex_init(&proc->files_lock); vec_init(&proc->identity.groups); } struct process* process_kernel() { if (!atomic_load(&kernel_process_ready, ATOMIC_ACQUIRE)) { process_init_common(&kernel_process); kernel_process.pid = 0; kernel_process.space = &kernel_vma; atomic_store(&kernel_process_ready, true, ATOMIC_RELEASE); } return &kernel_process; } static void close_files(struct process* proc) { struct fd_table* t = &proc->files; mutex_lock(&proc->files_lock); for (size_t w = 0; w < fd_words(t->capacity); w++) { for (uint64_t bits = t->open[w]; bits; bits &= bits - 1) vfs_file_unref(t->files[w * 64 + __builtin_ctzll(bits)]); } fd_table_destroy(t); mutex_unlock(&proc->files_lock); } void process_ref(struct process* proc) { refcount_inc(&proc->rc); } void process_unref(struct process* proc) { if (proc == &kernel_process || !refcount_dec(&proc->rc)) return; close_files(proc); vfs_path_drop(proc->root); vfs_path_drop(proc->cwd); if (proc->space) vma_unref(proc->space); vec_destroy(&proc->identity.groups); mem_free(proc); } uint8_t process_priority(struct process* proc) { if (proc == &kernel_process) return SCHED_PRIO_DEFAULT; // Normalize the priority from "nice" ranges to our priorities. return 16 + (19 - proc->nice) * 15 / 39; } void process_attach_task(struct process* proc, struct task* task) { spin_lock(&proc->tasks_lock); tailq_insert_tail(&proc->tasks, task, process_link); proc->thread_count++; spin_unlock(&proc->tasks_lock); } void process_detach_task(struct process* proc, struct task* task) { spin_lock(&proc->tasks_lock); tailq_remove(&proc->tasks, task, process_link); spin_unlock(&proc->tasks_lock); } static void rlimit_init(struct process_rlimit* limit, rlim_t cur, rlim_t max) { atomic_init(&limit->cur, cur); limit->max = max; } static errno_t process_alloc(struct process** out) { struct process* proc = mem_alloc(sizeof(struct process), 0); if (!proc) return ENOMEM; process_init_common(proc); proc->pid = atomic_fetch_add(&next_pid, 1, ATOMIC_RELAXED); proc->root = vfs_get_root(); proc->cwd = vfs_get_root(); proc->umask = 022; rlimit_init(&proc->limit_nofile, PROCESS_DEFAULT_FDS, PROCESS_MAX_FDS); *out = proc; return 0; } static errno_t load_image( struct process* proc, struct vmo* object, const char* path, const char** argv, const char** envp, struct vma** out_space, uintptr_t* out_ip, uintptr_t* out_sp ) { struct vma* space; errno_t status = vma_new(&space); if (status) return status; struct exec_info info = { .space = space, .file_obj = object, .exec_path = path, .argv = argv, .envp = envp, }; struct exec_format* format = exec_identify(object); status = format ? format->load(proc, &info, out_ip, out_sp) : ENOEXEC; if (status) { vma_unref(space); return status; } *out_space = space; return 0; } errno_t process_spawn_init(const char* path, const char** argv, const char** envp) { struct process* proc; errno_t status = process_alloc(&proc); if (status) return status; struct task_start_info* start = mem_alloc(sizeof(struct task_start_info), 0); if (!start) { process_unref(proc); return ENOMEM; } struct vmo* object; status = exec_open(proc, path, &object); if (!status) { status = load_image(proc, object, path, argv, envp, &proc->space, &start->ip, &start->sp); vmo_unref(object); } if (status) { mem_free(start); process_unref(proc); return status; } struct vfs_file* console; status = vfs_file_open(proc->root, proc->cwd, "/dev/console", O_RDWR, 0, ident_kernel(), &console); if (status) { log_warn("process: Unable to open /dev/console for init (%i)\n", status); } else { for (int fd = 0; fd <= 2; fd++) process_fd_replace(proc, fd, console, false); vfs_file_unref(console); } struct task* task; status = task_create(argv[0], proc, sched_to_user, (uintptr_t)start, &task); process_unref(proc); if (status) { mem_free(start); return status; } sched_add_task(&percpu_get()->sched, task); return 0; } errno_t process_fd_alloc(struct process* proc, struct vfs_file* file, bool cloexec, int* out_fd) { return process_fd_alloc_min(proc, file, 0, cloexec, out_fd); } size_t process_fd_limit(struct process* proc) { return MIN((size_t)PROCESS_MAX_FDS, (size_t)atomic_load(&proc->limit_nofile.cur, ATOMIC_RELAXED)); } errno_t process_fd_alloc_min(struct process* proc, struct vfs_file* file, int min_fd, bool cloexec, int* out_fd) { if (min_fd < 0 || (size_t)min_fd >= process_fd_limit(proc)) return EINVAL; struct fd_table* t = &proc->files; mutex_lock(&proc->files_lock); const size_t fd = fd_find_free(t, MAX((size_t)min_fd, t->next_free)); if (fd >= process_fd_limit(proc)) { mutex_unlock(&proc->files_lock); return EMFILE; } errno_t status = fd_table_grow(t, fd); if (status) { mutex_unlock(&proc->files_lock); return status; } fd_install(t, fd, file, cloexec); if ((size_t)min_fd <= t->next_free) t->next_free = fd + 1; vfs_file_ref(file); mutex_unlock(&proc->files_lock); *out_fd = fd; return 0; } errno_t process_fd_replace(struct process* proc, int fd, struct vfs_file* file, bool cloexec) { if (fd < 0 || (size_t)fd >= process_fd_limit(proc)) return EBADF; struct fd_table* t = &proc->files; mutex_lock(&proc->files_lock); errno_t status = fd_table_grow(t, fd); if (status) { mutex_unlock(&proc->files_lock); return status; } struct vfs_file* displaced = t->files[fd]; fd_install(t, fd, file, cloexec); vfs_file_ref(file); mutex_unlock(&proc->files_lock); if (displaced) vfs_file_unref(displaced); return 0; } errno_t process_fd_get_cloexec(struct process* proc, int fd, bool* out_cloexec) { errno_t status = EBADF; mutex_lock(&proc->files_lock); if (fd_lookup(proc, fd)) { *out_cloexec = (proc->files.cloexec[fd / 64] >> (fd % 64)) & 1; status = 0; } mutex_unlock(&proc->files_lock); return status; } errno_t process_fd_set_cloexec(struct process* proc, int fd, bool cloexec) { errno_t status = EBADF; mutex_lock(&proc->files_lock); struct vfs_file* file = fd_lookup(proc, fd); if (file) { fd_install(&proc->files, fd, file, cloexec); status = 0; } mutex_unlock(&proc->files_lock); return status; } struct vfs_file* process_fd_get(struct process* proc, int fd) { mutex_lock(&proc->files_lock); struct vfs_file* file = fd_lookup(proc, fd); if (file) vfs_file_ref(file); mutex_unlock(&proc->files_lock); return file; } errno_t process_fd_close(struct process* proc, int fd) { mutex_lock(&proc->files_lock); struct vfs_file* file = fd_lookup(proc, fd); if (file) fd_clear(&proc->files, fd); mutex_unlock(&proc->files_lock); if (!file) return EBADF; vfs_file_unref(file); return 0; }