/* * syscalls.c * * Copyright (C) 2016 Aleksandar Andrejevic * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include extern sysret_t syscall_function(const void*, dword_t*, dword_t); #include "syscall_table.inl" static void system_service_handler(registers_t *regs, byte_t int_num) { dword_t parameters[MAX_PARAMETERS]; thread_t *thread = get_current_thread(); if (regs->eax >= SERVICE_COUNT) { regs->eax = ERR_NOSYSCALL; return; } acquire_lock(&thread->syscall_lock); thread->syscall_regs = regs; if (get_previous_mode() == USER_MODE) { if (!check_usermode((dword_t*)regs->edx, sizeof(parameters))) { regs->eax = ERR_BADPTR; goto cleanup; } EH_TRY { memcpy(parameters, (dword_t*)regs->edx, sizeof(parameters)); } EH_CATCH { regs->eax = ERR_BADPTR; EH_ESCAPE(goto cleanup); } EH_DONE; } else { memcpy(parameters, (dword_t*)regs->edx, sizeof(parameters)); } sysret_t result = syscall_function(service_table[regs->eax], parameters, sizeof(parameters)); regs->eax = (dword_t)result; regs->edx = (dword_t)(result >> 32); cleanup: thread->syscall_regs = NULL; release_lock(&thread->syscall_lock); if (thread->cancel_io) while (TRUE) syscall_yield_quantum(); else if (thread->frozen) syscall_yield_quantum(); } processor_mode_t get_previous_mode() { thread_t *thread = get_current_thread(); return thread ? thread->previous_mode : KERNEL_MODE; } char *copy_user_string(const char *string) { int length = 0; ASSERT(get_previous_mode() == USER_MODE); EH_TRY length = strlen(string); EH_CATCH length = -1; EH_DONE; if (length == -1) return NULL; if (!check_usermode(string, length + 1)) return NULL; char *result = (char*)malloc(length + 1); if (result == NULL) return NULL; EH_TRY { strcpy(result, string); } EH_CATCH { free(result); result = NULL; } EH_DONE; return result; } sysret_t syscall(syscall_number_t num, ...) { int i; qword_t ret = 0ULL; dword_t parameters[MAX_PARAMETERS]; thread_t *thread = get_current_thread(); if (num >= SERVICE_COUNT) return ERR_NOSYSCALL; va_list params; va_start(params, num); for (i = 0; i < MAX_PARAMETERS; i++) parameters[i] = va_arg(params, dword_t); va_end(params); processor_mode_t old_mode = get_previous_mode(); if (thread) thread->previous_mode = KERNEL_MODE; ret = syscall_function(service_table[num], parameters, sizeof(parameters)); if (thread) thread->previous_mode = old_mode; return ret; } bool_t check_usermode(const void *pointer, dword_t size) { dword_t first_addr = PAGE_ALIGN((dword_t)pointer); dword_t last_addr = PAGE_ALIGN((dword_t)pointer + size - 1); if (first_addr >= USER_AREA_START && last_addr <= USER_AREA_END) return TRUE; else return FALSE; } void syscalls_init() { set_int_handler(SYSCALL_INTERRUPT, system_service_handler, TRUE, TRUE); }