514 lines
17 KiB
C
514 lines
17 KiB
C
#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 <stdio.h>
|
|
#include <syscall-nr.h>
|
|
|
|
#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. */
|
|
}
|
|
}
|