Fixes insert_file()
[akaros.git] / kern / src / vfs.c
index 38928d7..bc43859 100644 (file)
@@ -463,6 +463,8 @@ int path_lookup(char *path, int flags, struct nameidata *nd)
        int retval;
        printd("Path lookup for %s\n", path);
        /* we allow absolute lookups with no process context */
+       /* TODO: RCU read lock on pwd or kref_not_zero in a loop.  concurrent chdir
+        * could decref nd->dentry before we get to incref it below. */
        if (path[0] == '/') {                   /* absolute lookup */
                if (!current)
                        nd->dentry = default_ns.root->mnt_root;
@@ -715,7 +717,7 @@ void dentry_release(struct kref *kref)
 void __dentry_free(struct dentry *dentry)
 {
        if (dentry->d_inode)
-               printk("Freeing dentry %p: %s\n", dentry, dentry->d_name.name);
+               printd("Freeing dentry %p: %s\n", dentry, dentry->d_name.name);
        assert(dentry->d_op);   /* catch bugs.  a while back, some lacked d_op */
        dentry->d_op->d_release(dentry);
        /* TODO: check/test the boundaries on this. */
@@ -812,8 +814,10 @@ void dcache_put(struct super_block *sb, struct dentry *key_val)
        int retval;
        spin_lock(&sb->s_dcache_lock);
        old = hashtable_remove(sb->s_dcache, key_val);
-       if (old) {
-               assert(old->d_flags & DENTRY_NEGATIVE);
+       /* if it is old and non-negative, our caller lost a race with someone else
+        * adding the dentry.  but since we yanked it out, like a bunch of idiots,
+        * we still have to put it back.  should be fairly rare. */
+       if (old && (old->d_flags & DENTRY_NEGATIVE)) {
                /* This is possible, but rare for now (about to be put on the LRU) */
                assert(!(old->d_flags & DENTRY_USED));
                assert(!kref_refcnt(&old->d_kref));
@@ -956,6 +960,7 @@ void load_inode(struct dentry *dentry, unsigned long ino)
  * note we don't pass this an nd, like Linux does... */
 static struct inode *create_inode(struct dentry *dentry, int mode)
 {
+       uint64_t now = epoch_seconds();
        /* note it is the i_ino that uniquely identifies a file in the specific
         * filesystem.  there's a diff between creating an inode (even for an in-use
         * ino) and then filling it in, and vs creating a brand new one.
@@ -968,10 +973,10 @@ static struct inode *create_inode(struct dentry *dentry, int mode)
        inode->i_nlink = 1;
        inode->i_size = 0;
        inode->i_blocks = 0;
-       inode->i_atime.tv_sec = 0;              /* TODO: now! */
-       inode->i_ctime.tv_sec = 0;
-       inode->i_mtime.tv_sec = 0;
-       inode->i_atime.tv_nsec = 0;             /* are these supposed to be the extra ns? */
+       inode->i_atime.tv_sec = now;
+       inode->i_ctime.tv_sec = now;
+       inode->i_mtime.tv_sec = now;
+       inode->i_atime.tv_nsec = 0;
        inode->i_ctime.tv_nsec = 0;
        inode->i_mtime.tv_nsec = 0;
        inode->i_bdev = inode->i_sb->s_bdev;
@@ -1084,6 +1089,24 @@ void stat_inode(struct inode *inode, struct kstat *kstat)
        kstat->st_ctime = inode->i_ctime;
 }
 
+void print_kstat(struct kstat *kstat)
+{
+       printk("kstat info for %p:\n", kstat);
+       printk("\tst_dev    : %p\n", kstat->st_dev);
+       printk("\tst_ino    : %p\n", kstat->st_ino);
+       printk("\tst_mode   : %p\n", kstat->st_mode);
+       printk("\tst_nlink  : %p\n", kstat->st_nlink);
+       printk("\tst_uid    : %p\n", kstat->st_uid);
+       printk("\tst_gid    : %p\n", kstat->st_gid);
+       printk("\tst_rdev   : %p\n", kstat->st_rdev);
+       printk("\tst_size   : %p\n", kstat->st_size);
+       printk("\tst_blksize: %p\n", kstat->st_blksize);
+       printk("\tst_blocks : %p\n", kstat->st_blocks);
+       printk("\tst_atime  : %p\n", kstat->st_atime);
+       printk("\tst_mtime  : %p\n", kstat->st_mtime);
+       printk("\tst_ctime  : %p\n", kstat->st_ctime);
+}
+
 /* Inode Cache management.  In general, search on the ino, get a refcnt'd value
  * back.  Remove does not give you a reference back - it should only be called
  * in inode_release(). */
@@ -1136,19 +1159,23 @@ ssize_t generic_file_read(struct file *file, char *buf, size_t count,
        unsigned long first_idx, last_idx;
        size_t copy_amt;
        char *buf_end;
+       /* read in offset, in case of a concurrent reader/writer, so we don't screw
+        * up our math for count, the idxs, etc. */
+       off64_t orig_off = ACCESS_ONCE(*offset);
 
        /* Consider pushing some error checking higher in the VFS */
        if (!count)
                return 0;
-       if (*offset == file->f_dentry->d_inode->i_size)
+       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 */
-       if (*offset + count > file->f_dentry->d_inode->i_size) {
-               count = file->f_dentry->d_inode->i_size - *offset;
+       if (orig_off + count > file->f_dentry->d_inode->i_size) {
+               count = file->f_dentry->d_inode->i_size - orig_off;
        }
-       page_off = *offset & (PGSIZE - 1);
-       first_idx = *offset >> PGSHIFT;
-       last_idx = (*offset + count) >> PGSHIFT;
+       assert((long)count > 0);
+       page_off = orig_off & (PGSIZE - 1);
+       first_idx = orig_off >> PGSHIFT;
+       last_idx = (orig_off + count) >> PGSHIFT;
        buf_end = buf + count;
        /* For each file page, make sure it's in the page cache, then copy it out.
         * TODO: will probably need to consider concurrently truncated files here.*/
@@ -1171,7 +1198,10 @@ ssize_t generic_file_read(struct file *file, char *buf, size_t count,
                pm_put_page(page);      /* it's still in the cache, we just don't need it */
        }
        assert(buf == buf_end);
-       *offset += count;
+       /* could have concurrent file ops that screw with offset, so userspace isn't
+        * safe.  but at least it'll be a value that one of the concurrent ops could
+        * have produced (compared to *offset_changed_concurrently += count. */
+       *offset = orig_off + count;
        return count;
 }
 
@@ -1191,17 +1221,18 @@ ssize_t generic_file_write(struct file *file, const char *buf, size_t count,
        unsigned long first_idx, last_idx;
        size_t copy_amt;
        const char *buf_end;
+       off64_t orig_off = ACCESS_ONCE(*offset);
 
        /* Consider pushing some error checking higher in the VFS */
        if (!count)
                return 0;
        /* Extend the file.  Should put more checks in here, and maybe do this per
         * page in the for loop below. */
-       if (*offset + count > file->f_dentry->d_inode->i_size)
-               file->f_dentry->d_inode->i_size = *offset + count;
-       page_off = *offset & (PGSIZE - 1);
-       first_idx = *offset >> PGSHIFT;
-       last_idx = (*offset + count) >> PGSHIFT;
+       if (orig_off + count > file->f_dentry->d_inode->i_size)
+               file->f_dentry->d_inode->i_size = orig_off + count;
+       page_off = orig_off & (PGSIZE - 1);
+       first_idx = orig_off >> PGSHIFT;
+       last_idx = (orig_off + count) >> PGSHIFT;
        buf_end = buf + count;
        /* For each file page, make sure it's in the page cache, then write it.*/
        for (int i = first_idx; i <= last_idx; i++) {
@@ -1222,7 +1253,7 @@ ssize_t generic_file_write(struct file *file, const char *buf, size_t count,
                pm_put_page(page);      /* it's still in the cache, we just don't need it */
        }
        assert(buf == buf_end);
-       *offset += count;
+       *offset = orig_off + count;
        return count;
 }
 
@@ -1299,7 +1330,16 @@ struct file *do_file_open(char *path, int flags, int mode)
        nd->intent = LOOKUP_OPEN;
        error = path_lookup(path, LOOKUP_FOLLOW, nd);
        if (!error) {
-               /* Still need to make sure we didn't want to O_EXCL create */
+               /* 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))) {
+                       set_errno(EISDIR);
+                       goto out_path_only;
+               }
+               /* Also need to make sure we didn't want to O_EXCL create */
                if ((flags & O_CREAT) && (flags & O_EXCL)) {
                        set_errno(EEXIST);
                        goto out_path_only;
@@ -1534,22 +1574,17 @@ int do_access(char *path, int mode)
        return retval;
 }
 
-int do_chmod(char *path, int mode)
+int do_file_chmod(struct file *file, int mode)
 {
-       struct nameidata nd_r = {0}, *nd = &nd_r;
-       int retval = 0;
-       retval = path_lookup(path, 0, nd);
-       if (!retval) {
-               #if 0
-               /* TODO: when we have notions of uid, check for the proc's uid */
-               if (nd->dentry->d_inode->i_uid != UID_OF_ME)
-                       retval = -EPERM;
-               else
-               #endif
-                       nd->dentry->d_inode->i_mode |= mode & S_PMASK;
-       }
-       path_release(nd);       
-       return retval;
+       int old_mode_ftype = file->f_dentry->d_inode->i_mode & __S_IFMT;
+       #if 0
+       /* TODO: when we have notions of uid, check for the proc's uid */
+       if (file->f_dentry->d_inode->i_uid != UID_OF_ME)
+               retval = -EPERM;
+       else
+       #endif
+               file->f_dentry->d_inode->i_mode = (mode & S_PMASK) | old_mode_ftype;
+       return 0;
 }
 
 /* Make a directory at path with mode.  Returns -1 and sets errno on errors */
@@ -2040,6 +2075,52 @@ struct file *get_file_from_fd(struct files_struct *open_files, int file_desc)
        return retval;
 }
 
+/* Grow the vfs fd set */
+static int grow_fd_set(struct files_struct *open_files) {
+       int n;
+       struct file_desc *nfd, *ofd;
+
+       /* Only update open_fds once. If currently pointing to open_fds_init, then
+        * update it to point to a newly allocated fd_set with space for
+        * NR_FILE_DESC_MAX */
+       if (open_files->open_fds == (struct fd_set*)&open_files->open_fds_init) {
+               open_files->open_fds = kzmalloc(sizeof(struct fd_set), 0);
+               memmove(open_files->open_fds, &open_files->open_fds_init,
+                       sizeof(struct small_fd_set));
+       }
+
+       /* Grow the open_files->fd array in increments of NR_OPEN_FILES_DEFAULT */
+       n = open_files->max_files + NR_OPEN_FILES_DEFAULT;
+       if (n > NR_FILE_DESC_MAX)
+               n = NR_FILE_DESC_MAX;
+       nfd = kzmalloc(n * sizeof(struct file_desc), 0);
+       if (nfd == NULL)
+               return -1;
+
+       /* Move the old array on top of the new one */
+       ofd = open_files->fd;
+       memmove(nfd, ofd, open_files->max_files * sizeof(struct file_desc));
+
+       /* Update the array and the maxes for both max_files and max_fdset */
+       open_files->fd = nfd;
+       open_files->max_files = n;
+       open_files->max_fdset = n;
+
+       /* Only free the old one if it wasn't pointing to open_files->fd_array */
+       if (ofd != open_files->fd_array)
+               kfree(ofd);
+       return 0;
+}
+
+/* Free the vfs fd set if necessary */
+static void free_fd_set(struct files_struct *open_files) {
+       if (open_files->open_fds != (struct fd_set*)&open_files->open_fds_init) {
+               kfree(open_files->open_fds);
+               assert(open_files->fd != open_files->fd_array);
+               kfree(open_files->fd);
+       }
+}
+
 /* 9ns: puts back an FD from the VFS-FD-space. */
 int put_fd(struct files_struct *open_files, int file_desc)
 {
@@ -2076,7 +2157,7 @@ struct file *put_file_from_fd(struct files_struct *open_files, int file_desc)
                        assert(file_desc < open_files->max_files);
                        file = open_files->fd[file_desc].fd_file;
                        open_files->fd[file_desc].fd_file = 0;
-                       assert(file);
+                       assert(file);   /* 9ns shouldn't call this put */
                        kref_put(&file->f_kref);
                        CLR_BITMASK_BIT(open_files->open_fds->fds_bits, file_desc);
                }
@@ -2092,19 +2173,28 @@ static int __get_fd(struct files_struct *open_files, int low_fd)
                return -EINVAL;
        if (open_files->closed)
                return -EINVAL; /* won't matter, they are dying */
-       for (int i = low_fd; i < open_files->max_fdset; i++) {
-               if (GET_BITMASK_BIT(open_files->open_fds->fds_bits, i))
-                       continue;
-               slot = i;
-               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;
-               break;
-       }
-       if (slot == -1) /* should expand the FD array and fd_set */
-               warn("Ran out of file descriptors, deal with me!");
+
+       /* 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) {
+               for (low_fd; low_fd < open_files->max_fdset; low_fd++) {
+                       if (GET_BITMASK_BIT(open_files->open_fds->fds_bits, low_fd))
+                               continue;
+                       slot = 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;
+                       break;
+               }
+               if (slot == -1) {
+                       /* Expand the FD array and fd_set */
+                       if (grow_fd_set(open_files) == -1)
+                               return -ENOMEM;
+                       /* loop after growing */
+               }
+       }
        return slot;
 }
 
@@ -2118,13 +2208,60 @@ int get_fd(struct files_struct *open_files, int low_fd)
        return slot;
 }
 
+static int __claim_fd(struct files_struct *open_files, int file_desc)
+{
+       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) {
+               grow_fd_set(open_files);
+               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. */
+int claim_fd(struct files_struct *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;
+}
+
 /* Inserts the file in the files_struct, returning the corresponding new file
  * descriptor, or an error code.  We start looking for open fds from low_fd. */
-int insert_file(struct files_struct *open_files, struct file *file, int low_fd)
+int insert_file(struct files_struct *open_files, struct file *file, int low_fd,
+                bool must)
 {
-       int slot;
+       int slot, ret;
        spin_lock(&open_files->lock);
-       slot = __get_fd(open_files, low_fd);
+       if (must) {
+               ret = __claim_fd(open_files, low_fd);
+               if (ret < 0) {
+                       spin_unlock(&open_files->lock);
+                       return ret;
+               }
+               assert(!ret);   /* issues with claim_fd returning status, not the fd */
+               slot = low_fd;
+       } else {
+               slot = __get_fd(open_files, low_fd);
+       }
+
        if (slot < 0) {
                spin_unlock(&open_files->lock);
                return slot;
@@ -2148,8 +2285,6 @@ void close_all_files(struct files_struct *open_files, bool cloexec)
                spin_unlock(&open_files->lock);
                return;
        }
-       if (!cloexec)
-               open_files->closed = TRUE;
        for (int i = 0; i < open_files->max_fdset; i++) {
                if (GET_BITMASK_BIT(open_files->open_fds->fds_bits, i)) {
                        /* while max_files and max_fdset might not line up, we should never
@@ -2168,6 +2303,10 @@ void close_all_files(struct files_struct *open_files, bool cloexec)
                        CLR_BITMASK_BIT(open_files->open_fds->fds_bits, i);
                }
        }
+       if (!cloexec) {
+               free_fd_set(open_files);
+               open_files->closed = TRUE;
+       }
        spin_unlock(&open_files->lock);
 }
 
@@ -2196,8 +2335,9 @@ void clone_files(struct files_struct *src, struct files_struct *dst)
                        assert(i < dst->max_files && dst->fd[i].fd_file == 0);
                        SET_BITMASK_BIT(dst->open_fds->fds_bits, i);
                        dst->fd[i].fd_file = file;
-                       assert(file);
-                       kref_get(&file->f_kref, 1);
+                       /* no file means 9ns is using it, they clone separately */
+                       if (file)
+                               kref_get(&file->f_kref, 1);
                        if (i >= dst->next_fd)
                                dst->next_fd = i + 1;
                }
@@ -2206,6 +2346,20 @@ void clone_files(struct files_struct *src, struct files_struct *dst)
        spin_unlock(&src->lock);
 }
 
+static void __chpwd(struct fs_struct *fs_env, struct dentry *new_pwd)
+{
+       struct dentry *old_pwd;
+       kref_get(&new_pwd->d_kref, 1);
+       /* writer lock, make sure we replace pwd with ours.  could also CAS.
+        * readers don't lock at all, so they need to either loop, or we need to
+        * delay releasing old_pwd til an RCU grace period. */
+       spin_lock(&fs_env->lock);
+       old_pwd = fs_env->pwd;
+       fs_env->pwd = new_pwd;
+       spin_unlock(&fs_env->lock);
+       kref_put(&old_pwd->d_kref);
+}
+
 /* Change the working directory of the given fs env (one per process, at this
  * point).  Returns 0 for success, -ERROR for whatever error. */
 int do_chdir(struct fs_struct *fs_env, char *path)
@@ -2215,14 +2369,22 @@ int do_chdir(struct fs_struct *fs_env, char *path)
        retval = path_lookup(path, LOOKUP_DIRECTORY, nd);
        if (!retval) {
                /* nd->dentry is the place we want our PWD to be */
-               kref_get(&nd->dentry->d_kref, 1);
-               kref_put(&fs_env->pwd->d_kref);
-               fs_env->pwd = nd->dentry;
+               __chpwd(fs_env, nd->dentry);
        }
        path_release(nd);
        return retval;
 }
 
+int do_fchdir(struct fs_struct *fs_env, struct file *file)
+{
+       if ((file->f_dentry->d_inode->i_mode & __S_IFMT) != __S_IFDIR) {
+               set_errno(ENOTDIR);
+               return -1;
+       }
+       __chpwd(fs_env, file->f_dentry);
+       return 0;
+}
+
 /* Returns a null-terminated string of up to length cwd_l containing the
  * absolute path of fs_env, (up to fs_env's root).  Be sure to kfree the char*
  * "kfree_this" when you are done with it.  We do this since it's easier to
@@ -2258,7 +2420,7 @@ char *do_getcwd(struct fs_struct *fs_env, char **kfree_this, size_t cwd_l)
                        set_errno(ERANGE);
                        return 0;
                }
-               path_start -= link_len + 1;     /* the 1 is for the \0 */
+               path_start -= link_len;
                strncpy(path_start, dentry->d_name.name, link_len);
                path_start--;
                *path_start = '/';