Enable the PFM sampling to pass an 64bit info value
[akaros.git] / kern / src / vfs.c
index 666c008..7ed5651 100644 (file)
@@ -5,6 +5,7 @@
  * Default implementations and global values for the VFS. */
 
 #include <vfs.h> // keep this first
+#include <ros/errno.h>
 #include <sys/queue.h>
 #include <assert.h>
 #include <stdio.h>
@@ -17,6 +18,7 @@
 #include <umem.h>
 #include <smp.h>
 #include <ns.h>
+#include <fdtap.h>
 
 struct sb_tailq super_blocks = TAILQ_HEAD_INITIALIZER(super_blocks);
 spinlock_t super_blocks_lock = SPINLOCK_INITIALIZER;
@@ -141,6 +143,39 @@ char *file_name(struct file *file)
        return file->f_dentry->d_name.name;
 }
 
+static int prepend(char **pbuf, size_t *pbuflen, const char *str, size_t len)
+{
+       if (*pbuflen < len)
+               return -ENAMETOOLONG;
+       *pbuflen -= len;
+       *pbuf -= len;
+       memcpy(*pbuf, str, len);
+
+       return 0;
+}
+
+char *dentry_path(struct dentry *dentry, char *path, size_t max_size)
+{
+       size_t csize = max_size;
+       char *path_start = path + max_size, *base;
+
+       if (prepend(&path_start, &csize, "\0", 1) < 0 || csize < 1)
+               return NULL;
+       /* Handle the case that the passed dentry is the root. */
+       base = path_start - 1;
+       *base = '/';
+       while (!DENTRY_IS_ROOT(dentry)) {
+               if (prepend(&path_start, &csize, dentry->d_name.name,
+                                       dentry->d_name.len) < 0 ||
+                       prepend(&path_start, &csize, "/", 1) < 0)
+                       return NULL;
+               base = path_start;
+               dentry = dentry->d_parent;
+       }
+
+       return base;
+}
+
 /* Some issues with this, coupled closely to fs_lookup.
  *
  * Note the use of __dentry_free, instead of kref_put.  In those cases, we don't
@@ -630,14 +665,12 @@ static void dentry_set_name(struct dentry *dentry, char *name)
        size_t name_len = strnlen(name, MAX_FILENAME_SZ);       /* not including \0! */
        char *l_name = 0;
        if (name_len < DNAME_INLINE_LEN) {
-               strncpy(dentry->d_iname, name, name_len);
-               dentry->d_iname[name_len] = '\0';
+               strlcpy(dentry->d_iname, name, name_len + 1);
                qstr_builder(dentry, 0);
        } else {
                l_name = kmalloc(name_len + 1, 0);
                assert(l_name);
-               strncpy(l_name, name, name_len);
-               l_name[name_len] = '\0';
+               strlcpy(l_name, name, name_len + 1);
                qstr_builder(dentry, l_name);
        }
 }
@@ -1196,6 +1229,10 @@ ssize_t generic_file_read(struct file *file, char *buf, size_t count,
        /* Consider pushing some error checking higher in the VFS */
        if (!count)
                return 0;
+       if (!(file->f_flags & O_READ)) {
+               set_errno(EBADF);
+               return 0;
+       }
        if (orig_off >= file->f_dentry->d_inode->i_size)
                return 0; /* EOF */
        /* Make sure we don't go past the end of the file */
@@ -1213,16 +1250,12 @@ ssize_t generic_file_read(struct file *file, char *buf, size_t count,
                error = pm_load_page(file->f_mapping, i, &page);
                assert(!error); /* TODO: handle ENOMEM and friends */
                copy_amt = MIN(PGSIZE - page_off, buf_end - buf);
-               /* TODO: (UMEM) think about this.  if it's a user buffer, we're relying
-                * on current to detect whose it is (which should work for async calls).
-                * Also, need to propagate errors properly...  Probably should do a
-                * user_mem_check, then free, and also to make a distinction between
-                * when the kernel wants a read/write (TODO: KFOP) */
-               if (current) {
+               /* TODO: (KFOP) Probably shouldn't do this.  Either memcpy directly, or
+                * split out the is_user_r(w)addr from copy_{to,from}_user() */
+               if (!is_ktask(per_cpu_info[core_id()].cur_kthread))
                        memcpy_to_user(current, buf, page2kva(page) + page_off, copy_amt);
-               } else {
+               else
                        memcpy(buf, page2kva(page) + page_off, copy_amt);
-               }
                buf += copy_amt;
                page_off = 0;
                pm_put_page(page);      /* it's still in the cache, we just don't need it */
@@ -1256,6 +1289,10 @@ ssize_t generic_file_write(struct file *file, const char *buf, size_t count,
        /* Consider pushing some error checking higher in the VFS */
        if (!count)
                return 0;
+       if (!(file->f_flags & O_WRITE)) {
+               set_errno(EBADF);
+               return 0;
+       }
        if (file->f_flags & O_APPEND) {
                spin_lock(&file->f_dentry->d_inode->i_lock);
                orig_off = file->f_dentry->d_inode->i_size;
@@ -1282,14 +1319,11 @@ ssize_t generic_file_write(struct file *file, const char *buf, size_t count,
                error = pm_load_page(file->f_mapping, i, &page);
                assert(!error); /* TODO: handle ENOMEM and friends */
                copy_amt = MIN(PGSIZE - page_off, buf_end - buf);
-               /* TODO: (UMEM) (KFOP) think about this.  if it's a user buffer, we're
-                * relying on current to detect whose it is (which should work for async
-                * calls). */
-               if (current) {
+               /* TODO: (UMEM) (KFOP) think about this. */
+               if (!is_ktask(per_cpu_info[core_id()].cur_kthread))
                        memcpy_from_user(current, page2kva(page) + page_off, buf, copy_amt);
-               } else {
+               else
                        memcpy(page2kva(page) + page_off, buf, copy_amt);
-               }
                buf += copy_amt;
                page_off = 0;
                atomic_or(&page->pg_flags, PG_DIRTY);
@@ -1317,6 +1351,10 @@ ssize_t generic_dir_read(struct file *file, char *u_buf, size_t count,
        }
        if (!count)
                return 0;
+       if (!(file->f_flags & O_READ)) {
+               set_errno(EBADF);
+               return 0;
+       }
        /* start readdir from where it left off: */
        dirent->d_off = *offset;
        for (   ;
@@ -1332,11 +1370,10 @@ ssize_t generic_dir_read(struct file *file, char *u_buf, size_t count,
                }
                /* Slight info exposure: could be extra crap after the name in the
                 * dirent (like the name of a deleted file) */
-               if (current) {
+               if (!is_ktask(per_cpu_info[core_id()].cur_kthread))
                        memcpy_to_user(current, u_buf, dirent, sizeof(struct dirent));
-               } else {
+               else
                        memcpy(u_buf, dirent, sizeof(struct dirent));
-               }
                amt_copied += sizeof(struct dirent);
                /* 0 signals end of directory */
                if (retval == 0)
@@ -1374,12 +1411,7 @@ struct file *do_file_open(char *path, int flags, int mode)
        nd->intent = LOOKUP_OPEN;
        error = path_lookup(path, LOOKUP_FOLLOW, nd);
        if (!error) {
-               /* If this is a directory, make sure we are opening with O_RDONLY.
-                * Unfortunately we can't just check for O_RDONLY directly because its
-                * value is 0x0.  We instead have to make sure it's not O_WRONLY and
-                * not O_RDWR explicitly. */
-               if (S_ISDIR(nd->dentry->d_inode->i_mode) &&
-                   ((flags & O_WRONLY) || (flags & O_RDWR))) {
+               if (S_ISDIR(nd->dentry->d_inode->i_mode) && (flags & O_WRITE)) {
                        set_errno(EISDIR);
                        goto out_path_only;
                }
@@ -1648,6 +1680,14 @@ int do_mkdir(char *path, int mode)
        int error;
        int retval = -1;
 
+       /* The dir might exist and might be /, so we can't look for the parent */
+       nd->intent = LOOKUP_OPEN;
+       error = path_lookup(path, LOOKUP_FOLLOW, nd);
+       path_release(nd);
+       if (!error) {
+               set_errno(EEXIST);
+               return -1;
+       }
        nd->intent = LOOKUP_CREATE;
        /* get the parent, but don't follow links */
        error = path_lookup(path, LOOKUP_PARENT, nd);
@@ -1655,12 +1695,6 @@ int do_mkdir(char *path, int mode)
                set_errno(-error);
                goto out_path_only;
        }
-       /* see if the target is already there, handle accordingly */
-       dentry = do_lookup(nd->dentry, nd->last.name); 
-       if (dentry) {
-               set_errno(EEXIST);
-               goto out_dentry;
-       }
        /* Doesn't already exist, let's try to make it: */
        dentry = get_dentry(nd->dentry->d_sb, nd->dentry, nd->last.name);
        if (!dentry)
@@ -2229,21 +2263,9 @@ struct file *dentry_open(struct dentry *dentry, int flags)
        struct file *file;
        int desired_mode;
        inode = dentry->d_inode;
-       /* Do the mode first, since we can still error out.  f_mode stores how the
-        * OS file is open, which can be more restrictive than the i_mode */
-       switch (flags & (O_RDONLY | O_WRONLY | O_RDWR)) {
-               case O_RDONLY:
-                       desired_mode = S_IRUSR;
-                       break;
-               case O_WRONLY:
-                       desired_mode = S_IWUSR;
-                       break;
-               case O_RDWR:
-                       desired_mode = S_IRUSR | S_IWUSR;
-                       break;
-               default:
-                       goto error_access;
-       }
+       /* f_mode stores how the OS file is open, which can be more restrictive than
+        * the i_mode */
+       desired_mode = omode_to_rwx(flags & O_ACCMODE);
        if (check_perms(inode, desired_mode))
                goto error_access;
        file = alloc_file();
@@ -2297,11 +2319,12 @@ void file_release(struct kref *kref)
 
 ssize_t kread_file(struct file *file, void *buf, size_t sz)
 {
-       /* TODO: (KFOP) (VFS kernel read/writes need to have no proc current) */
-       struct proc *old_proc = switch_to(0);
+       /* TODO: (KFOP) (VFS kernel read/writes need to be from a ktask) */
+       uintptr_t old_ret = switch_to_ktask();
        off64_t dummy = 0;
        ssize_t cpy_amt = file->f_op->read(file, buf, sz, &dummy);
-       switch_back(0, old_proc);
+
+       switch_back_from_ktask(old_ret);
        return cpy_amt;
 }
 
@@ -2434,31 +2457,12 @@ static void free_fd_set(struct fd_table *open_files)
        }
 }
 
-/* 9ns: puts back an FD from the VFS-FD-space. */
-int put_fd(struct fd_table *open_files, int file_desc)
-{
-       if (file_desc < 0) {
-               warn("Negative FD!\n");
-               return 0;
-       }
-       spin_lock(&open_files->lock);
-       if (file_desc < open_files->max_fdset) {
-               if (GET_BITMASK_BIT(open_files->open_fds->fds_bits, file_desc)) {
-                       /* while max_files and max_fdset might not line up, we should never
-                        * have a valid fdset higher than files */
-                       assert(file_desc < open_files->max_files);
-                       CLR_BITMASK_BIT(open_files->open_fds->fds_bits, file_desc);
-               }
-       }
-       spin_unlock(&open_files->lock);
-       return 0;
-}
-
 /* If FD is in the group, remove it, decref it, and return TRUE. */
 bool close_fd(struct fd_table *fdt, int fd)
 {
        struct file *file = 0;
        struct chan *chan = 0;
+       struct fd_tap *tap = 0;
        bool ret = FALSE;
        if (fd < 0)
                return FALSE;
@@ -2470,9 +2474,13 @@ bool close_fd(struct fd_table *fdt, int fd)
                        assert(fd < fdt->max_files);
                        file = fdt->fd[fd].fd_file;
                        chan = fdt->fd[fd].fd_chan;
+                       tap = fdt->fd[fd].fd_tap;
                        fdt->fd[fd].fd_file = 0;
                        fdt->fd[fd].fd_chan = 0;
+                       fdt->fd[fd].fd_tap = 0;
                        CLR_BITMASK_BIT(fdt->open_fds->fds_bits, fd);
+                       if (fd < fdt->hint_min_fd)
+                               fdt->hint_min_fd = fd;
                        ret = TRUE;
                }
        }
@@ -2482,6 +2490,8 @@ bool close_fd(struct fd_table *fdt, int fd)
                kref_put(&file->f_kref);
        else
                cclose(chan);
+       if (tap)
+               kref_put(&tap->kref);
        return ret;
 }
 
@@ -2490,15 +2500,21 @@ void put_file_from_fd(struct fd_table *open_files, int file_desc)
        close_fd(open_files, file_desc);
 }
 
-static int __get_fd(struct fd_table *open_files, int low_fd)
+static int __get_fd(struct fd_table *open_files, int low_fd, bool must_use_low)
 {
        int slot = -1;
        int error;
+       bool update_hint = TRUE;
        if ((low_fd < 0) || (low_fd > NR_FILE_DESC_MAX))
                return -EINVAL;
        if (open_files->closed)
                return -EINVAL; /* won't matter, they are dying */
-
+       if (must_use_low && GET_BITMASK_BIT(open_files->open_fds->fds_bits, low_fd))
+               return -ENFILE;
+       if (low_fd > open_files->hint_min_fd)
+               update_hint = FALSE;
+       else
+               low_fd = open_files->hint_min_fd;
        /* Loop until we have a valid slot (we grow the fd_array at the bottom of
         * the loop if we haven't found a slot in the current array */
        while (slot == -1) {
@@ -2509,8 +2525,9 @@ static int __get_fd(struct fd_table *open_files, int low_fd)
                        SET_BITMASK_BIT(open_files->open_fds->fds_bits, slot);
                        assert(slot < open_files->max_files &&
                               open_files->fd[slot].fd_file == 0);
-                       if (slot >= open_files->next_fd)
-                               open_files->next_fd = slot + 1;
+                       /* We know slot >= hint, since we started with the hint */
+                       if (update_hint)
+                               open_files->hint_min_fd = slot + 1;
                        break;
                }
                if (slot == -1) {
@@ -2521,77 +2538,15 @@ static int __get_fd(struct fd_table *open_files, int low_fd)
        return slot;
 }
 
-/* Gets and claims a free FD, used by 9ns.  < 0 == error.  cloexec is tracked on
- * the VFS FD.  It's value will be O_CLOEXEC (not 1) or 0. */
-int get_fd(struct fd_table *open_files, int low_fd, int cloexec)
-{
-       int slot;
-       spin_lock(&open_files->lock);
-       slot = __get_fd(open_files, low_fd);
-       if (cloexec && (slot >= 0))
-               open_files->fd[slot].fd_flags |= FD_CLOEXEC;
-       spin_unlock(&open_files->lock);
-       return slot;
-}
-
-static int __claim_fd(struct fd_table *open_files, int file_desc)
-{
-       int error;
-       if ((file_desc < 0) || (file_desc > NR_FILE_DESC_MAX))
-               return -EINVAL;
-       if (open_files->closed)
-               return -EINVAL; /* won't matter, they are dying */
-
-       /* Grow the open_files->fd_set until the file_desc can fit inside it */
-       while(file_desc >= open_files->max_files) {
-               if ((error = grow_fd_set(open_files)))
-                       return error;
-               cpu_relax();
-       }
-
-       /* If we haven't grown, this could be a problem, so check for it */
-       if (GET_BITMASK_BIT(open_files->open_fds->fds_bits, file_desc))
-               return -ENFILE; /* Should never really happen. Here to catch bugs. */
-
-       SET_BITMASK_BIT(open_files->open_fds->fds_bits, file_desc);
-       assert(file_desc < open_files->max_files &&
-              open_files->fd[file_desc].fd_file == 0);
-       if (file_desc >= open_files->next_fd)
-               open_files->next_fd = file_desc + 1;
-       return 0;
-}
-
-/* Claims a specific FD when duping FDs. used by 9ns.  < 0 == error.  No need
- * for cloexec here, since it's not used during dup. */
-int claim_fd(struct fd_table *open_files, int file_desc)
-{
-       int ret;
-       spin_lock(&open_files->lock);
-       ret = __claim_fd(open_files, file_desc);
-       spin_unlock(&open_files->lock);
-       return ret;
-}
-
 /* Insert a file or chan (obj, chosen by vfs) into the fd group with fd_flags.
  * If must_use_low, then we have to insert at FD = low_fd.  o/w we start looking
  * for empty slots at low_fd. */
 int insert_obj_fdt(struct fd_table *fdt, void *obj, int low_fd, int fd_flags,
                    bool must_use_low, bool vfs)
 {
-       int slot, ret;
+       int slot;
        spin_lock(&fdt->lock);
-       if (must_use_low) {
-               ret = __claim_fd(fdt, low_fd);
-               if (ret < 0) {
-                       spin_unlock(&fdt->lock);
-                       return ret;
-               }
-               assert(!ret);   /* issues with claim_fd returning status, not the fd */
-               slot = low_fd;
-       } else {
-               slot = __get_fd(fdt, low_fd);
-       }
-
+       slot = __get_fd(fdt, low_fd, must_use_low);
        if (slot < 0) {
                spin_unlock(&fdt->lock);
                return slot;
@@ -2625,7 +2580,17 @@ int insert_file(struct fd_table *open_files, struct file *file, int low_fd,
 }
 
 /* Closes all open files.  Mostly just a "put" for all files.  If cloexec, it
- * will only close the FDs with FD_CLOEXEC (opened with O_CLOEXEC or fcntld). */
+ * will only close the FDs with FD_CLOEXEC (opened with O_CLOEXEC or fcntld).
+ *
+ * Notes on concurrency:
+ * - Can't hold spinlocks while we call cclose, since it might sleep eventually.
+ * - We're called from proc_destroy, so we could have concurrent openers trying
+ *   to add to the group (other syscalls), hence the "closed" flag.
+ * - dot and slash chans are dealt with in proc_free.  its difficult to close
+ *   and zero those with concurrent syscalls, since those are a source of krefs.
+ * - Once we lock and set closed, no further additions can happen.  To simplify
+ *   our closes, we also allow multiple calls to this func (though that should
+ *   never happen with the current code). */
 void close_fdt(struct fd_table *fdt, bool cloexec)
 {
        struct file *file;
@@ -2650,6 +2615,8 @@ void close_fdt(struct fd_table *fdt, bool cloexec)
                                continue;
                        file = fdt->fd[i].fd_file;
                        chan = fdt->fd[i].fd_chan;
+                       to_close[idx].fd_tap = fdt->fd[i].fd_tap;
+                       fdt->fd[i].fd_tap = 0;
                        if (file) {
                                fdt->fd[i].fd_file = 0;
                                to_close[idx++].fd_file = file;
@@ -2660,6 +2627,8 @@ void close_fdt(struct fd_table *fdt, bool cloexec)
                        CLR_BITMASK_BIT(fdt->open_fds->fds_bits, i);
                }
        }
+       /* it's just a hint, we can build back up from being 0 */
+       fdt->hint_min_fd = 0;
        if (!cloexec) {
                free_fd_set(fdt);
                fdt->closed = TRUE;
@@ -2673,6 +2642,8 @@ void close_fdt(struct fd_table *fdt, bool cloexec)
                        kref_put(&to_close[i].fd_file->f_kref);
                else
                        cclose(to_close[i].fd_chan);
+               if (to_close[i].fd_tap)
+                       kref_put(&to_close[i].fd_tap->kref);
        }
        kfree(to_close);
 }
@@ -2709,10 +2680,9 @@ void clone_fdt(struct fd_table *src, struct fd_table *dst)
                                kref_get(&file->f_kref, 1);
                        else
                                chan_incref(chan);
-                       if (i >= dst->next_fd)
-                               dst->next_fd = i + 1;
                }
        }
+       dst->hint_min_fd = src->hint_min_fd;
        spin_unlock(&dst->lock);
        spin_unlock(&src->lock);
 }
@@ -2795,7 +2765,7 @@ char *do_getcwd(struct fs_struct *fs_env, char **kfree_this, size_t cwd_l)
                        return 0;
                }
                path_start -= link_len;
-               strncpy(path_start, dentry->d_name.name, link_len);
+               memmove(path_start, dentry->d_name.name, link_len);
                path_start--;
                *path_start = '/';
                dentry = dentry->d_parent;