Catch negative FDs
[akaros.git] / kern / src / mm.c
index 2d17605..90a53c4 100644 (file)
@@ -104,8 +104,8 @@ struct vm_region *split_vmr(struct vm_region *old_vmr, uintptr_t va)
        new_vmr->vm_prot = old_vmr->vm_prot;
        new_vmr->vm_flags = old_vmr->vm_flags;
        if (old_vmr->vm_file) {
+               kref_get(&old_vmr->vm_file->f_kref, 1);
                new_vmr->vm_file = old_vmr->vm_file;
-               atomic_inc(&new_vmr->vm_file->f_refcnt);
                new_vmr->vm_foff = old_vmr->vm_foff +
                                      old_vmr->vm_end - old_vmr->vm_base;
        } else {
@@ -181,7 +181,7 @@ int shrink_vmr(struct vm_region *vmr, uintptr_t va)
 void destroy_vmr(struct vm_region *vmr)
 {
        if (vmr->vm_file)
-               atomic_dec(&vmr->vm_file->f_refcnt);
+               kref_put(&vmr->vm_file->f_kref);
        TAILQ_REMOVE(&vmr->vm_proc->vm_regions, vmr, vm_link);
        kmem_cache_free(vmr_kcache, vmr);
 }
@@ -226,6 +226,15 @@ void isolate_vmrs(struct proc *p, uintptr_t va, size_t len)
                split_vmr(vmr, va + len);
 }
 
+/* Destroys all vmrs of a process - important for when files are mmap()d and
+ * probably later when we share memory regions */
+void destroy_vmrs(struct proc *p)
+{
+       struct vm_region *vm_i;
+       TAILQ_FOREACH(vm_i, &p->vm_regions, vm_link)
+               destroy_vmr(vm_i);
+}
+
 /* This will make new_p have the same VMRs as p, though it does nothing to
  * ensure the physical pages or whatever are shared/mapped/copied/whatever.
  * This is used by fork().
@@ -244,9 +253,9 @@ void duplicate_vmrs(struct proc *p, struct proc *new_p)
                vmr->vm_end = vm_i->vm_end;
                vmr->vm_prot = vm_i->vm_prot;   
                vmr->vm_flags = vm_i->vm_flags; 
+               if (vm_i->vm_file)
+                       kref_get(&vm_i->vm_file->f_kref, 1);
                vmr->vm_file = vm_i->vm_file;
-               if (vmr->vm_file)
-                       atomic_inc(&vmr->vm_file->f_refcnt);
                vmr->vm_foff = vm_i->vm_foff;
                TAILQ_INSERT_TAIL(&new_p->vm_regions, vmr, vm_link);
        }
@@ -258,9 +267,9 @@ void print_vmrs(struct proc *p)
        struct vm_region *vmr;
        printk("VM Regions for proc %d\n", p->pid);
        TAILQ_FOREACH(vmr, &p->vm_regions, vm_link)
-               printk("%02d: (0x%08x - 0x%08x): %08p, %08p, %08p\n", count++,
+               printk("%02d: (0x%08x - 0x%08x): %08p, %08p, %08p, %08p\n", count++,
                       vmr->vm_base, vmr->vm_end, vmr->vm_prot, vmr->vm_flags,
-                      vmr->vm_file);
+                      vmr->vm_file, vmr->vm_foff);
 }
 
 
@@ -272,11 +281,6 @@ void *mmap(struct proc *p, uintptr_t addr, size_t len, int prot, int flags,
        struct file *file = NULL;
        printd("mmap(addr %x, len %x, prot %x, flags %x, fd %x, off %x)\n", addr,
               len, prot, flags, fd, offset);
-       if (fd >= 0 && (flags & MAP_SHARED)) {
-               printk("[kernel] mmap() for files requires !MAP_SHARED.\n");
-               set_errno(current_tf, EACCES);
-               return MAP_FAILED;
-       }
        if (fd >= 0 && (flags & MAP_ANON)) {
                set_errno(current_tf, EBADF);
                return MAP_FAILED;
@@ -290,7 +294,7 @@ void *mmap(struct proc *p, uintptr_t addr, size_t len, int prot, int flags,
                return MAP_FAILED;
        }
        if (fd != -1) {
-               file = file_open_from_fd(p, fd);
+               file = get_file_from_fd(&p->open_files, fd);
                if (!file) {
                        set_errno(current_tf, EBADF);
                        return MAP_FAILED;
@@ -299,7 +303,7 @@ void *mmap(struct proc *p, uintptr_t addr, size_t len, int prot, int flags,
        addr = MAX(addr, MMAP_LOWEST_VA);
        void *result = do_mmap(p, addr, len, prot, flags, file, offset);
        if (file)
-               file_decref(file);
+               kref_put(&file->f_kref);
        return result;
 }
 
@@ -337,8 +341,17 @@ void *__do_mmap(struct proc *p, uintptr_t addr, size_t len, int prot, int flags,
        }
        vmr->vm_prot = prot;
        vmr->vm_flags = flags;
+       if (file)
+               kref_get(&file->f_kref, 1);
        vmr->vm_file = file;
        vmr->vm_foff = offset;
+       /* Prep the FS to make sure it can mmap the file.  Slightly weird semantics:
+        * they will have a hole in their VM now. */
+       if (file && file->f_op->mmap(file, vmr)) {
+               destroy_vmr(vmr);
+               set_errno(current_tf, EACCES);  /* not quite */
+               return MAP_FAILED;
+       }
        addr = vmr->vm_base;            /* so we know which pages to populate later */
        vmr = merge_me(vmr);            /* attempts to merge with neighbors */
        /* Fault in pages now if MAP_POPULATE - die on failure.  We want to populate
@@ -499,15 +512,13 @@ int handle_page_fault(struct proc* p, uintptr_t va, int prot)
  * faulted for a different reason (was mprotected on another core), and the
  * shootdown is on its way.  Userspace should have waited for the mprotect to
  * return before trying to write (or whatever), so we don't care and will fault
- * them.
- *
- * We did away with mmapping too much of a file, and will map an entire page, if
- * that file is big enough.  The alternative is to zerofill the last bit if the
- * vmr had a lesser length.  This makes shared mappings and mappings backed by
- * the FS problematic. */
+ * them. */
 int __handle_page_fault(struct proc* p, uintptr_t va, int prot)
 {
        struct vm_region *vmr;
+       struct page *a_page;
+       unsigned int f_idx;     /* index of the missing page in the file */
+       int retval = 0;
        /* Check the vmr's protection */
        vmr = find_vmr(p, va);
        if (!vmr)                                                       /* not mapped at all */
@@ -529,35 +540,38 @@ int __handle_page_fault(struct proc* p, uintptr_t va, int prot)
                panic("Swapping not supported!");
                return 0;
        }
-       /* allocate a page; maybe zero-fill it */
-       bool zerofill = (vmr->vm_file == NULL);
-       page_t *a_page;
-       if (upage_alloc(p, &a_page, zerofill))
-               return -ENOMEM;
-       /* if this isn't a zero-filled page, read it in from file.  it is the FS's
-        * responsibility to zero out the end of the last page if the EOF is not at
-        * the end of the page.
-        *
-        * TODO: (BLK) doing this while holding the mem lock!  prob want to block
-        * and return to userspace if it's not in the buffer cache.  will want to
-        * set a flag in the vmr so that subsequent faults will know the work is in
-        * progress. */
-       if (!zerofill) {
-               int foffset = ROUNDDOWN(va, PGSIZE) - vmr->vm_base + vmr->vm_foff;
-               int read_len = file_read_page(vmr->vm_file, page2pa(a_page), foffset);
-               if (read_len < 0) {
-                       page_free(a_page);
-                       return read_len;                        /* pass out the error code, for now */
+       if (!vmr->vm_file) {
+               /* No file - just want anonymous memory */
+               if (upage_alloc(p, &a_page, TRUE))
+                       return -ENOMEM;
+       } else {
+               /* Load the file's page in the page cache.
+                * TODO: (BLK) Note, we are holding the mem lock!  We need to rewrite
+                * this stuff so we aren't hold the lock as excessively as we are, and
+                * such that we can block and resume later. */
+               f_idx = (va - vmr->vm_base + vmr->vm_foff) >> PGSHIFT;
+               retval = file_load_page(vmr->vm_file, f_idx, &a_page);
+               if (retval)
+                       return retval;
+               /* If we want a private map that is writable, we'll preemptively give
+                * you a new page.  In the future, we want to CoW this, but the kernel
+                * needs to be able to handle its own page faults first. */
+               if ((vmr->vm_flags |= MAP_PRIVATE) && (vmr->vm_prot |= PROT_WRITE)) {
+                       struct page *cache_page = a_page;
+                       if (upage_alloc(p, &a_page, FALSE))
+                               return -ENOMEM;
+                       memcpy(page2kva(a_page), page2kva(cache_page), PGSIZE);
                }
                /* if this is an executable page, we might have to flush the instruction
                 * cache if our HW requires it. */
                if (vmr->vm_prot & PROT_EXEC)
                        icache_flush_page((void*)va, page2kva(a_page));
        }
-       /* update the page table */
+       /* update the page table TODO: careful with MAP_PRIVATE etc.  might do this
+        * separately (file, no file) */
        int pte_prot = (vmr->vm_prot & PROT_WRITE) ? PTE_USER_RW :
                       (vmr->vm_prot & (PROT_READ|PROT_EXEC)) ? PTE_USER_RO : 0;
-       page_incref(a_page);
+       page_incref(a_page);    /* incref, since we manually insert in the pgdir */
        *pte = PTE(page2ppn(a_page), PTE_P | pte_prot);
        return 0;
 }