VFS truncate
authorBarret Rhoden <brho@cs.berkeley.edu>
Thu, 21 Aug 2014 17:53:44 +0000 (10:53 -0700)
committerBarret Rhoden <brho@cs.berkeley.edu>
Thu, 21 Aug 2014 17:53:44 +0000 (10:53 -0700)
Also flushes the PM on O_TRUNC and touches up some sync on the file
size, since it just needed a spinlock.  Too bad the VFS was built before
we had semaphores, though it probably still would be rather racy.

Note that KFS won't write pages back, so any modifications to a file
won't exist outside of the page cache.  This isn't particularly related
to truncates extending a file, since the same thing happens with
write().

kern/include/vfs.h
kern/src/kfs.c
kern/src/syscall.c
kern/src/vfs.c

index dc39582..2072c99 100644 (file)
@@ -474,6 +474,7 @@ int do_mkdir(char *path, int mode);
 int do_rmdir(char *path);
 int do_pipe(struct file **pipe_files, int flags);
 int do_rename(char *old_path, char *new_path);
+int do_truncate(struct inode *inode, off64_t len);
 struct file *dentry_open(struct dentry *dentry, int flags);
 void file_release(struct kref *kref);
 
index e5bfc53..41d1019 100644 (file)
@@ -158,6 +158,7 @@ int kfs_readpage(struct page_map *pm, struct page *page)
 
 int kfs_writepage(struct page_map *pm, struct page *page)
 {
+       warn_once("KFS writepage does not save file contents!\n");
        return -1;
 }
 
@@ -495,6 +496,11 @@ char *kfs_readlink(struct dentry *dentry)
 /* Modifies the size of the file of inode to whatever its i_size is set to */
 void kfs_truncate(struct inode *inode)
 {
+       struct kfs_i_info *k_i_info = (struct kfs_i_info*)inode->i_fs_info;
+       /* init_size tracks how much of the file KFS has.  everything else is 0s.
+        * we only need to update it if we are dropping data.  as with other data
+        * beyond init_size, KFS will not save it during a write page! */
+       k_i_info->init_size = MIN(k_i_info->init_size, inode->i_size);
 }
 
 /* Checks whether the the access mode is allowed for the file belonging to the
@@ -545,8 +551,7 @@ void kfs_d_iput(struct dentry *dentry, struct inode *inode)
 
 /* file_operations */
 
-/* Updates the file pointer.  KFS doesn't let you go past the end of a file
- * yet, so it won't let you seek past either.  TODO: think about locking. */
+/* Updates the file pointer.  TODO: think about locking. */
 int kfs_llseek(struct file *file, off64_t offset, off64_t *ret, int whence)
 {
        off64_t temp_off = 0;
index 28f7833..b17f266 100644 (file)
@@ -1807,11 +1807,9 @@ static int vfs_wstat(struct file *file, uint8_t *stat_m, size_t stat_sz,
                        goto out;
        }
        if (flags & WSTAT_LENGTH) {
-               printk("Got truncate for file %s to length %d\n", file_name(file),
-                      dir->length);
-               /* Fail for now */
-               retval = -1;
-               goto out;
+               retval = do_truncate(file->f_dentry->d_inode, dir->length);
+               if (retval < 0)
+                       goto out;
        }
        if (flags & WSTAT_ATIME) {
                /* wstat only gives us seconds */
index 8dde42f..3193b45 100644 (file)
@@ -1236,8 +1236,14 @@ ssize_t generic_file_write(struct file *file, const char *buf, size_t count,
                return 0;
        /* Extend the file.  Should put more checks in here, and maybe do this per
         * page in the for loop below. */
-       if (orig_off + count > file->f_dentry->d_inode->i_size)
-               file->f_dentry->d_inode->i_size = orig_off + count;
+       if (orig_off + count > file->f_dentry->d_inode->i_size) {
+               /* lock for writes to i_size.  we allow lockless reads.  checking the
+                * i_size again in case of concurrent writers since our orig check.  */
+               spin_lock(&file->f_dentry->d_inode->i_lock);
+               if (orig_off + count > file->f_dentry->d_inode->i_size)
+                       file->f_dentry->d_inode->i_size = orig_off + count;
+               spin_unlock(&file->f_dentry->d_inode->i_lock);
+       }
        page_off = orig_off & (PGSIZE - 1);
        first_idx = orig_off >> PGSHIFT;
        last_idx = (orig_off + count) >> PGSHIFT;
@@ -1333,6 +1339,7 @@ struct file *do_file_open(char *path, int flags, int mode)
        struct inode *parent_i;
        struct nameidata nd_r = {0}, *nd = &nd_r;
        int error;
+       unsigned long nr_pages;
 
        /* The file might exist, lets try to just open it right away */
        nd->intent = LOOKUP_OPEN;
@@ -1398,8 +1405,11 @@ open_the_file:
        /* now open the file (freshly created or if it already existed).  At this
         * point, file_d is a refcnt'd dentry, regardless of which branch we took.*/
        if (flags & O_TRUNC) {
+               spin_lock(&file_d->d_inode->i_lock);
+               nr_pages = ROUNDUP(file_d->d_inode->i_size, PGSIZE) >> PGSHIFT;
                file_d->d_inode->i_size = 0;
-               /* TODO: probably should remove the garbage pages from the page map */
+               spin_unlock(&file_d->d_inode->i_lock);
+               pm_remove_contig(file_d->d_inode->i_mapping, 0, nr_pages);
        }
        file = dentry_open(file_d, flags);                              /* sets errno */
        /* Note the fall through to the exit paths.  File is 0 by default and if
@@ -2130,6 +2140,42 @@ out_old_path:
        return retval;
 }
 
+int do_truncate(struct inode *inode, off64_t len)
+{
+       off64_t old_len;
+       uint64_t now;
+       if (len < 0) {
+               set_errno(EINVAL);
+               return -1;
+       }
+       if (len > PiB) {
+               printk("[kernel] truncate for > petabyte, probably a bug\n");
+               /* continuing, not too concerned.  could set EINVAL or EFBIG */
+       }
+       spin_lock(&inode->i_lock);
+       old_len = inode->i_size;
+       if (old_len == len) {
+               spin_unlock(&inode->i_lock);
+               return 0;
+       }
+       inode->i_size = len;
+       /* truncate can't block, since we're holding the spinlock.  but it can rely
+        * on that lock being held */
+       inode->i_op->truncate(inode);
+       spin_unlock(&inode->i_lock);
+
+       if (old_len < len) {
+               pm_remove_contig(inode->i_mapping, old_len >> PGSHIFT,
+                                (len >> PGSHIFT) - (old_len >> PGSHIFT));
+       }
+       now = epoch_seconds();
+       inode->i_ctime.tv_sec = now;
+       inode->i_mtime.tv_sec = now;
+       inode->i_ctime.tv_nsec = 0;
+       inode->i_mtime.tv_nsec = 0;
+       return 0;
+}
+
 struct file *alloc_file(void)
 {
        struct file *file = kmem_cache_alloc(file_kcache, 0);