#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 static unsigned fd_counter = MIN_USER_FD; 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 *ptr, size_t size); /* A struct defining a syscall_function pointer along with its arity. */ typedef struct { syscall_function function; /* Function pointer. */ int arity; /* Number of arguments of the function. */ } syscall_arguments; /* A look-up table mapping numbers to system call functions with their number of arguments. */ static const 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 (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 a 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, 1); unsigned syscall_number = *(int *) f->esp; /* Ensures the number corresponds to a system call that can be handled. */ if (syscall_number >= LOOKUP_SIZE) thread_exit (); 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[3] = {0}; for (int i=0; i < syscall.arity; 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 ()->exit_status = status; thread_exit (); } /* Executes a given command with the relevant args, by calling process_execute. Acquires the filesystem lock as process_execute accesses the file system. Returns PID for the process that is running the CMD_LINE */ static pid_t syscall_exec (const char *cmd_line) { validate_user_pointer (cmd_line, 1); lock_acquire (&filesys_lock); pid_t pid = process_execute(cmd_line); lock_release (&filesys_lock); return pid; } /* 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); } /* 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 UNUSED, unsigned initial_size UNUSED) { validate_user_pointer (file, 1); lock_acquire (&filesys_lock); bool status = filesys_create (file, initial_size); lock_release (&filesys_lock); 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_user_pointer (file, 1); lock_acquire (&filesys_lock); bool status = filesys_remove (file); lock_release (&filesys_lock); 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_user_pointer (file, 1); lock_acquire (&filesys_lock); struct file *ptr = filesys_open (file); lock_release (&filesys_lock); if (ptr == NULL) return -1; /* 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) return -1; /* Populate the above struct, with a unique FD and the current open file */ file_info->fd = 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) { struct open_file *file_info = fd_get_file (fd); if (file_info == NULL) return -1; lock_acquire (&filesys_lock); int bytes = file_length (file_info->file); lock_release (&filesys_lock); 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 < 0 || fd == STDOUT_FILENO) return -1; validate_user_pointer (buffer, size); if (fd == STDIN_FILENO) { /* Reading from the console. */ char *write_buffer = buffer; for (int i = 0; i < size; i++) write_buffer[i] = input_getc (); return size; } else { /* Reading from a file. */ struct open_file *file_info = fd_get_file (fd); if (file_info == NULL) return -1; lock_acquire (&filesys_lock); int bytes_written = file_read (file_info->file, buffer, size); lock_release (&filesys_lock); 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_user_pointer (buffer, size); if (fd == STDOUT_FILENO) { /* Writing to the console. */ putbuf (buffer, size); return size; } else { /* Writing to a file. */ struct open_file *file_info = fd_get_file (fd); if (file_info == NULL) return 0; lock_acquire (&filesys_lock); int bytes = file_write (file_info->file, buffer, size); lock_release (&filesys_lock); 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) { struct open_file *file_info = fd_get_file (fd); if (file_info != NULL) { lock_acquire (&filesys_lock); file_seek (file_info->file, position); lock_release (&filesys_lock); } } /* 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) { struct open_file *file_info = fd_get_file (fd); if (file_info == NULL) return 0; lock_acquire (&filesys_lock); unsigned pos = file_tell (file_info->file); lock_release (&filesys_lock); 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) { struct open_file *file_info = fd_get_file (fd); if (file_info != NULL) { hash_delete (&thread_current ()->open_files, &file_info->elem); lock_acquire (&filesys_lock); file_close (file_info->file); lock_release (&filesys_lock); 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) { return hash_int (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; } /* 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 PTR and of size SIZE bytes is fully contained within user virtual memory. Kills the thread (by calling thread_exit) if the memory is invalid. Otherwise, returns the PTR given. If the size is 0, the function does no checks and returns PTR.*/ static void * validate_user_pointer (const void *ptr, size_t size) { if (size > 0 && (ptr == NULL || !is_user_vaddr (ptr) || !is_user_vaddr (ptr + size - 1) || pagedir_get_page (thread_current()->pagedir, ptr) == NULL)) thread_exit (); return (void *) ptr; }