#include "userprog/syscall.h" #include "devices/shutdown.h" #include "devices/input.h" #include "filesys/file.h" #include "filesys/filesys.h" #include "threads/vaddr.h" #include "threads/interrupt.h" #include "threads/malloc.h" #include "threads/thread.h" #include "threads/synch.h" #include "userprog/process.h" #include "userprog/pagedir.h" #include #include #define MAX_SYSCALL_ARGS 3 #define EXIT_FAILURE -1 struct open_file { int fd; /* File Descriptor / Identifier */ struct file *file; /* Pointer to the associated file */ struct hash_elem elem; /* elem for a hash table */ }; static void syscall_handler (struct intr_frame *); /* A syscall_function is a function that receives up to 3 arguments, the arguments to the functions are either ints or pointers taking up to 32 bits in size. */ typedef uintptr_t (*syscall_function) (uintptr_t, uintptr_t, uintptr_t); /* System call function prototypes */ static void syscall_halt (void); static void syscall_exit (int status); static pid_t syscall_exec (const char *cmd_line); static int syscall_wait (pid_t pid); static bool syscall_create (const char *file, unsigned initial_size); static bool syscall_remove (const char *file); static int syscall_open (const char *file); static int syscall_filesize (int fd); static int syscall_read (int fd, void *buffer, unsigned size); static int syscall_write (int fd, const void *buffer, unsigned size); static void syscall_seek (int fd, unsigned position); static unsigned syscall_tell (int fd); static void syscall_close (int fd); static struct open_file *fd_get_file (int fd); static void validate_user_pointer (const void *start, size_t size); static void validate_user_string (const char *str); /* A struct defining a syscall_function pointer along with its arity. */ struct syscall_arguments { syscall_function function; /* Function pointer. */ int arity; /* Number of arguments of the function. */ }; /* A look-up table mapping numbers to system call functions with their number of arguments. */ static const struct syscall_arguments syscall_lookup[] = { [SYS_HALT] = {(syscall_function) syscall_halt, 0}, [SYS_EXIT] = {(syscall_function) syscall_exit, 1}, [SYS_EXEC] = {(syscall_function) syscall_exec, 1}, [SYS_WAIT] = {(syscall_function) syscall_wait, 1}, [SYS_CREATE] = {(syscall_function) syscall_create, 2}, [SYS_REMOVE] = {(syscall_function) syscall_remove, 1}, [SYS_OPEN] = {(syscall_function) syscall_open, 1}, [SYS_FILESIZE] = {(syscall_function) syscall_filesize, 1}, [SYS_READ] = {(syscall_function) syscall_read, 3}, [SYS_WRITE] = {(syscall_function) syscall_write, 3}, [SYS_SEEK] = {(syscall_function) syscall_seek, 2}, [SYS_TELL] = {(syscall_function) syscall_tell, 1}, [SYS_CLOSE] = {(syscall_function) syscall_close, 1}, }; /* The number of syscall functions (i.e, number of elements) within the syscall_lookup table. */ static const int LOOKUP_SIZE = sizeof (syscall_lookup) / sizeof (struct syscall_arguments); /* Initialises the syscall handling system, as well as a global lock to synchronise all file access between processes. */ void syscall_init (void) { intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall"); lock_init (&filesys_lock); } /* Function that takes an interrupt frame containing a syscall and its args. Validates the arguments and pointers before calling the relevant high-level system call function, storing its output (if any) in f->eax */ static void syscall_handler (struct intr_frame *f) { /* First, read the system call number from the stack. */ validate_user_pointer (f->esp, sizeof (uintptr_t)); uintptr_t syscall_number = *(int *) f->esp; /* Ensures the number corresponds to a system call that can be handled. */ if (syscall_number >= LOOKUP_SIZE) syscall_exit (EXIT_FAILURE); struct syscall_arguments syscall = syscall_lookup[syscall_number]; /* Next, read and copy the arguments from the stack pointer. */ validate_user_pointer (f->esp + sizeof (uintptr_t), syscall.arity * sizeof (uintptr_t)); uintptr_t args[MAX_SYSCALL_ARGS] = {0}; for (int i = 0; i < syscall.arity && i < MAX_SYSCALL_ARGS; i++) args[i] = *(uintptr_t *) (f->esp + sizeof (uintptr_t) * (i + 1)); /* Call the function that handles this system call with the arguments. When there is a return value it is stored in f->eax. */ f->eax = syscall.function (args[0], args[1], args[2]); } /* Called upon a "halt" syscall, resulting in a complete shutdown of the process, via shutdown_power_off (); */ static void syscall_halt (void) { shutdown_power_off (); } static void syscall_exit (int status) { /* Sets exit_status of the thread to status. thread_exit () will call process_exit () if user programs are allowed. */ thread_current ()->result->exit_status = status; thread_exit (); } /* Executes a given command with the relevant args, by calling process_execute. Returns PID for the process that is running the CMD_LINE. */ static pid_t syscall_exec (const char *cmd_line) { /* Validate the user string before executing the process. */ validate_user_string (cmd_line); return process_execute (cmd_line); /* Returns the PID of the new process */ } /* Handles the syscall of wait. Effectively a wrapper for process_wait as the necessary validation and such all happens in process_wait anyway. */ static int syscall_wait (pid_t pid) { return process_wait (pid); /* Returns the exit status of the waited process */ } /* Handles the syscall for file creation. First validates the user file pointer. Acquires the file system lock to prevent synchronisation issues, and then uses FILESYS_CREATE to create the file, returning the same status */ static bool syscall_create (const char *file, unsigned initial_size) { /* Validate the user string before creating the file. */ validate_user_string (file); /* Acquire the file system lock to prevent race conditions. */ lock_acquire (&filesys_lock); bool status = filesys_create (file, initial_size); lock_release (&filesys_lock); /* Return the status of the file creation. */ return status; } /* Handles the syscall for file removal. First validates the user file pointer. Acquires the file system lock to prevent synchronisation issues, and then uses FILESYS_REMOVE to remove the file, returning the same success status */ static bool syscall_remove (const char *file) { /* Validate the user string before removing the file. */ validate_user_string (file); /* Acquire the file system lock to prevent race conditions. */ lock_acquire (&filesys_lock); bool status = filesys_remove (file); lock_release (&filesys_lock); /* Return the status of the file removal. */ return status; } /* Handles the syscall for opening a file connection. First, validates the file pointer. Then it acquires a lock for the file system, in order to open the connection without synchronisation issues. It then maps a new fd to this file in the hash table before returning the fd. */ static int syscall_open (const char *file) { /* Validate the user string before opening the file. */ validate_user_string (file); /* Acquire the file system lock to prevent race conditions. */ lock_acquire (&filesys_lock); struct file *ptr = filesys_open (file); lock_release (&filesys_lock); /* If the file could not be opened, return failure. */ if (ptr == NULL) return EXIT_FAILURE; /* Allocate space for a struct representing a mapping from an FD to a struct file. */ struct open_file *file_info = (struct open_file*) malloc (sizeof (struct open_file)); if (file_info == NULL) { /* If we could not allocate memory for the file_info struct, close the file and return failure. */ file_close (ptr); return EXIT_FAILURE; } /* Populate the above struct, with a unique FD and the current open file */ file_info->fd = thread_current ()->fd_counter++; file_info->file = ptr; /* Add the new FD->file mapping to the hashtable for the current thread */ hash_insert (&thread_current ()->open_files, &file_info->elem); /* Return the new FD */ return file_info->fd; } /* Handles the syscall for getting a file's size. Converts a provided FD into the asssociated file struct. Acquire the lock for the filesystem and use FILE_LENGTH to calculate the length for return. */ static int syscall_filesize (int fd) { /* Try to get the file from the FD. If it does not exist, return failure. */ struct open_file *file_info = fd_get_file (fd); if (file_info == NULL) return EXIT_FAILURE; /* Acquire the file system lock to prevent any race conditions. */ lock_acquire (&filesys_lock); int bytes = file_length (file_info->file); lock_release (&filesys_lock); /* Return the number of bytes in the file. */ return bytes; } /* Handles the syscall for reading SIZE bytes from a file referenced by FD. If the FD references the console, use input_getc (), otherwise convert the FD to its associated file struct, acquire the filesystem lock, read up to SIZE bytes and then return the number of bytes read.*/ static int syscall_read (int fd, void *buffer, unsigned size) { /* Only console (fd = 0) or other files, not including STDOUT, (fd > 1) are allowed. */ if (fd < STDIN_FILENO || fd == STDOUT_FILENO) return EXIT_FAILURE; /* Validate the user buffer for the provided size before reading. */ validate_user_pointer (buffer, size); if (fd == STDIN_FILENO) { /* Reading from the console. */ char *write_buffer = buffer; for (unsigned i = 0; i < size; i++) write_buffer[i] = input_getc (); /* In case of console, read is always (eventually) successful. So return the size for the number of bytes read. */ return size; } else { /* Reading from a file. */ /* Find the file from the FD. If it does not exist, return failure. */ struct open_file *file_info = fd_get_file (fd); if (file_info == NULL) return EXIT_FAILURE; /* Acquire the file system lock to prevent race-conditions. */ lock_acquire (&filesys_lock); int bytes_written = file_read (file_info->file, buffer, size); lock_release (&filesys_lock); /* Return the number of bytes read. */ return bytes_written; } } /* Handles the syscall for writing SIZE bytes to a file referenced by FD. If the FD references the console, use put_buf (), otherwise convert the FD to its associated file struct, acquire the filesystem lock, write up to SIZE bytes and then return the number of bytes written.*/ static int syscall_write (int fd, const void *buffer, unsigned size) { /* Only console (fd = 1) or other files, not including STDIN, (fd > 1) are allowed. */ if (fd <= 0) return 0; /* Validate the user buffer for the provided size before writing. */ validate_user_pointer (buffer, size); if (fd == STDOUT_FILENO) { /* Writing to the console. */ putbuf (buffer, size); /* In case of console, write is always successful. So return the size for the number of bytes written. */ return size; } else { /* Writing to a file. */ /* Find the file from the FD. If it does not exist, return failure. */ struct open_file *file_info = fd_get_file (fd); if (file_info == NULL) return 0; /* Acquire the file system lock to prevent race conditions. */ lock_acquire (&filesys_lock); int bytes = file_write (file_info->file, buffer, size); lock_release (&filesys_lock); /* Return the number of bytes written. */ return bytes; } } /* Handles the syscall for seeking to POSITION bytes in a file referenced by FD. Converts the FD to its associated file struct, acquires the filesystem lock and then uses file_seek to adjust the cursor to a specific position in the file.*/ static void syscall_seek (int fd, unsigned position) { /* Find the file from the FD. If it does not exist, do nothing. */ struct open_file *file_info = fd_get_file (fd); if (file_info != NULL) file_seek (file_info->file, position); } /* Handles the syscall for returning the next byte in a file referenced by FD. Converts the FD to its associated file struct, acquires the filesystem lock and then uses file_tell to read the next byte.*/ static unsigned syscall_tell (int fd) { /* Find the file from the FD. If it does not exist, return 0. */ struct open_file *file_info = fd_get_file (fd); if (file_info == NULL) return 0; unsigned pos = file_tell (file_info->file); /* Return the current position in the file. */ return pos; } /* Handles the syscall for closing a connection to a file. Converts the FD to its associated file struct. If it exists, it removes it from the hash table, acquires the filesystem lock, and uses file_close to close the connection.*/ static void syscall_close (int fd) { /* Find the file from the FD. If it does not exist, do nothing. */ struct open_file *file_info = fd_get_file (fd); if (file_info != NULL) { /* File exists */ /* First, remove the file from the hash table of open files. */ hash_delete (&thread_current ()->open_files, &file_info->elem); /* Then, close the file, acquiring the file system lock to prevent race conditions. */ lock_acquire (&filesys_lock); file_close (file_info->file); lock_release (&filesys_lock); /* Free the memory allocated for the file_info struct. */ free (file_info); } } /* Hashing function needed for the open_file table. Returns a hash for an entry, based on its FD. */ unsigned fd_hash (const struct hash_elem *element, void *aux UNUSED) { /* We use the FD as the hash value. This is because the FD is incremented sequentially and is therefore unique for each file. It positively affects the performance of the hash table: 1. It is unique so no need to call expensive hash functions. 2. It being sequential means that the hash table is more likely to be weight balanced. */ return hash_entry (element, struct open_file, elem)->fd; } /* Comparator function for the open_file table. Compares two entries based on the FDs. */ bool fd_less (const struct hash_elem *a_, const struct hash_elem *b_, void *aux UNUSED) { struct open_file *a = hash_entry (a_, struct open_file, elem); struct open_file *b = hash_entry (b_, struct open_file, elem); return a->fd < b->fd; } /* Function to clean up an open file entry. Closes the file and frees the associated memory. */ void fd_cleanup (struct hash_elem *e, void *aux UNUSED) { struct open_file *file_info = hash_entry (e, struct open_file, elem); lock_acquire (&filesys_lock); file_close (file_info->file); lock_release (&filesys_lock); free (file_info); } /* Gets a file from its descriptor (FD number). If there is no file with the fd FD it returns NULL. */ static struct open_file * fd_get_file (int fd) { /* We have to set up a fake open_file in order to be able to search the hash table. See hash.h. */ struct open_file fake_file_info; fake_file_info.fd = fd; struct hash_elem *e = hash_find (&thread_current ()->open_files, &fake_file_info.elem); if (e == NULL) return NULL; return hash_entry (e, struct open_file, elem); } /* Validates if a block of memory starting at START and of size SIZE bytes is fully contained within user virtual memory. Kills the thread (by exiting with failure) if the memory is invalid. Otherwise, returns (nothing) normally. If the size is 0, the function does no checks and returns the given ptr. */ static void validate_user_pointer (const void *start, size_t size) { /* If the size is 0, we do not need to check anything. */ if (size == 0) return; const void *end = start + size - 1; /* Check if the start and end pointers are valid user virtual addresses. */ if (start == NULL || !is_user_vaddr (start) || !is_user_vaddr (end)) syscall_exit (EXIT_FAILURE); /* We now need to check if the entire memory block is mapped to physical memory by the page table. */ for (const void *ptr = pg_round_down (start); ptr <= end; ptr += PGSIZE) if (pagedir_get_page (thread_current ()->pagedir, ptr) == NULL) syscall_exit (EXIT_FAILURE); } /* Validates if a string is fully contained within user virtual memory. Kills the thread (by exiting with failure) if the memory is invalid. Otherwise, returns (nothing) normally. */ static void validate_user_string (const char *str) { /* Check if the string pointer is a valid user virtual address. */ if (str == NULL || !is_user_vaddr (str)) syscall_exit (EXIT_FAILURE); /* Calculate the offset of the string within the (first) page. */ size_t offset = (uintptr_t) str % PGSIZE; /* We move page by page, checking if the page is mapped to physical memory. */ for (;;) { void *page = pg_round_down (str); /* If we reach addresses that are not mapped to physical memory before the end of the string, the thread is terminated. */ if (!is_user_vaddr(page) || pagedir_get_page (thread_current ()->pagedir, page) == NULL) syscall_exit (EXIT_FAILURE); while (offset < PGSIZE) { if (*str == '\0') return; /* We reached the end of the string without issues. */ str++; offset++; } offset = 0; /* Next page will start at the beginning. */ } }