generic_file_write() and file holes
authorBarret Rhoden <brho@cs.berkeley.edu>
Mon, 19 Jul 2010 18:31:17 +0000 (11:31 -0700)
committerKevin Klues <klueska@cs.berkeley.edu>
Thu, 3 Nov 2011 00:35:48 +0000 (17:35 -0700)
readpage() is supposed to handle hole (use a 0'd page).  KFS can handle
extending files and dealing with the holes, though they won't get saved,
which may be an issue once we start evicting from the page cache.  Also,
there are no constraints on file sizes or anything like that.

Documentation/vfs.txt
kern/include/kfs.h
kern/include/vfs.h
kern/src/kfs.c
kern/src/vfs.c

index b3bffb1..4d1ee48 100644 (file)
@@ -192,3 +192,16 @@ A note on refcnting.  When a page is added to the page cache, that's a stored
 reference.  When you lookup a page in the page cache, you get a refcnt'd
 reference back.  When you pull a page from the page cache, you also get a
 refcnt'd reference back - specifically it is the ref that was in the page map.
+
+Files with Holes
+--------------------------
+If a file has a hole, we'll still try to look up the page in the page cache.
+When that doesn't happen, we'll create and add a page, then call readpage().
+Readpage will realize there is no page on disk/backing store for that part of
+the file (since it was a hole) and just memset the page to 0.  In the future, we
+can consider getting a CoW 0-page, but that's a bit premature and a bookkeeping
+pain.
+
+This also applies to trying to write to a block beyond the EOF.  If the request
+hits the page cache and readpage(), it's because it was already checked and
+cleared in another part of the VFS, such as in generic_file_write().
index 83bfdbe..40be441 100644 (file)
@@ -25,6 +25,7 @@ extern struct fs_type kfs_fs_type;
 struct kfs_i_info {
        struct dentry_tailq             children;               /* our childrens */
        void                                    *filestart;             /* or our file location */
+       size_t                                  init_size;              /* file size on the backing store */
 };
 
 /* Old KFS below here */
@@ -41,4 +42,4 @@ ssize_t kfs_lookup_path(char*NTS path);
 struct proc *kfs_proc_create(int kfs_inode);
 void kfs_cat(int kfs_inode);
 
-#endif // !ROS_KERN_KFS_H
+#endif /* !ROS_KERN_KFS_H */
index 7d8348f..4d1215a 100644 (file)
@@ -427,6 +427,8 @@ void dcache_put(struct dentry *dentry);
 /* File functions */
 ssize_t generic_file_read(struct file *file, char *buf, size_t count,
                           off_t *offset);
+ssize_t generic_file_write(struct file *file, const char *buf, size_t count,
+                           off_t *offset);
 
 /* Page cache functions */
 struct page *pm_find_page(struct page_map *pm, unsigned long index);
index 68d19fb..63da657 100644 (file)
@@ -118,13 +118,20 @@ struct fs_type kfs_fs_type = {"KFS", 0, kfs_get_sb, kfs_kill_sb, {0, 0},
  * future. */
 int kfs_readpage(struct file *file, struct page *page)
 {
+       size_t pg_idx_byte = page->pg_index * PGSIZE;
        struct kfs_i_info *k_i_info = (struct kfs_i_info*)file->f_inode->i_fs_info;
-       uintptr_t begin = (size_t)k_i_info->filestart + page->pg_index * PGSIZE;
-       /* Need to be careful we don't copy beyond the EOF, and that we zero out
-        * whatever is left over.  The memset is a noop when copy_amt == PGSIZE. */
-       size_t copy_amt = MIN(PGSIZE, file->f_inode->i_size - begin);
-       memcpy(page2kva(page), (void*)begin, copy_amt);
-       memset(page2kva(page) + copy_amt, 0, PGSIZE - copy_amt);
+       uintptr_t begin = (size_t)k_i_info->filestart + pg_idx_byte;
+       /* If we're beyond the initial start point, we just need a zero page.  This
+        * is for a hole or for extending a file (even though it won't be saved).
+        * Otherwise, we want the data from KFS, being careful to not copy from
+        * beyond the original EOF (and zero padding anything extra). */
+       if (pg_idx_byte >= k_i_info->init_size) {
+               memset(page2kva(page), 0, PGSIZE);
+       } else {
+               size_t copy_amt = MIN(PGSIZE, k_i_info->init_size - pg_idx_byte);
+               memcpy(page2kva(page), (void*)begin, copy_amt);
+               memset(page2kva(page) + copy_amt, 0, PGSIZE - copy_amt);
+       }
        /* This is supposed to be done in the IO system when the operation is
         * complete.  Since we aren't doing a real IO request, and it is already
         * done, we can do it here. */
@@ -535,45 +542,6 @@ off_t kfs_llseek(struct file *file, off_t offset, int whence)
        return temp_off;
 }
 
-/* Writes count bytes from buf to the file, starting at *offset, which is
- * increased accordingly */
-ssize_t kfs_write(struct file *file, const char *buf, size_t count,
-                  off_t *offset)
-{
-       struct kfs_i_info *k_i_info = (struct kfs_i_info*)file->f_inode->i_fs_info;
-       void *begin, *end;
-
-       /* TODO: handle this higher up in the VFS */
-       if (!count)
-               return 0;
-       if (*offset == file->f_inode->i_size)
-               return 0; /* EOF */
-
-       /* Make sure we don't go past the end of the file */
-       if (*offset + count > file->f_inode->i_size) {
-               count = file->f_inode->i_size - *offset;
-       }
-       begin = k_i_info->filestart + *offset;
-       end = begin + count;
-       if ((begin < k_i_info->filestart) ||
-           (end > k_i_info->filestart + file->f_inode->i_size) ||
-           (end < begin)) {
-               set_errno(current_tf, EINVAL);
-               return -1;
-       }
-
-       /* TODO: 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).  Consider
-        * pushing this up the VFS stack. */
-       if (current) {
-               memcpy_from_user(current, begin, buf, count);
-       } else {
-               memcpy(begin, buf, count);
-       }
-       *offset += count;
-       return count;
-}
-
 /* Fills in the next directory entry (dirent), starting with d_off.  Like with
  * read and write, there will be issues with userspace and the *dirent buf.
  * TODO: we don't really do anything with userspace concerns here, in part
@@ -767,7 +735,7 @@ struct dentry_operations kfs_d_op = {
 struct file_operations kfs_f_op = {
        kfs_llseek,
        generic_file_read,
-       kfs_write,
+       generic_file_write,
        kfs_readdir,
        kfs_mmap,
        kfs_open,
@@ -924,6 +892,8 @@ static int __add_kfs_entry(struct dentry *parent, char *path,
                        inode = dentry->d_inode;
                        ((struct kfs_i_info*)inode->i_fs_info)->filestart =
                                                                c_bhdr->c_filestart;
+                       ((struct kfs_i_info*)inode->i_fs_info)->init_size =
+                                                               c_bhdr->c_filesize;
                }
                /* Set other info from the CPIO entry */
                inode->i_uid = c_bhdr->c_uid;
index bb2415e..308407f 100644 (file)
@@ -244,7 +244,8 @@ void dcache_put(struct dentry *dentry)
 
 /* Read count bytes from the file into buf, starting at *offset, which is increased
  * accordingly, returning the number of bytes transfered.  Most filesystems will
- * use this function for their f_op->read.  Note, this uses the page cache. */
+ * use this function for their f_op->read.  Note, this uses the page cache.
+ * Want to try out page remapping later on... */
 ssize_t generic_file_read(struct file *file, char *buf, size_t count,
                           off_t *offset)
 {
@@ -269,7 +270,7 @@ ssize_t generic_file_read(struct file *file, char *buf, size_t count,
        last_idx = (*offset + 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 */
+        * TODO: will probably need to consider concurrently truncated files here.*/
        for (int i = first_idx; i <= last_idx; i++) {
                error = file_load_page(file, i, &page);
                assert(!error); /* TODO: handle ENOMEM and friends */
@@ -290,6 +291,53 @@ ssize_t generic_file_read(struct file *file, char *buf, size_t count,
        return count;
 }
 
+/* Write count bytes from buf to the file, starting at *offset, which is increased
+ * accordingly, returning the number of bytes transfered.  Most filesystems will
+ * use this function for their f_op->write.  Note, this uses the page cache.
+ * Changes don't get flushed to disc til there is an fsync, page cache eviction,
+ * or other means of trying to writeback the pages. */
+ssize_t generic_file_write(struct file *file, const char *buf, size_t count,
+                           off_t *offset)
+{
+       struct page *page;
+       int error;
+       off_t page_off;
+       unsigned long first_idx, last_idx;
+       size_t copy_amt;
+       const char *buf_end;
+
+       /* 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_inode->i_size)
+               file->f_inode->i_size = *offset + count;
+       page_off = *offset & (PGSIZE - 1);
+       first_idx = *offset >> PGSHIFT;
+       last_idx = (*offset + 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++) {
+               error = file_load_page(file, i, &page);
+               assert(!error); /* TODO: handle ENOMEM and friends */
+               copy_amt = MIN(PGSIZE - page_off, buf_end - buf);
+               /* TODO: 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) {
+                       memcpy_to_user(current, page2kva(page) + page_off, buf, copy_amt);
+               } else {
+                       memcpy(page2kva(page) + page_off, buf, copy_amt);
+               }
+               buf += copy_amt;
+               page_off = 0;
+               page_decref(page);      /* it's still in the cache, we just don't need it */
+       }
+       assert(buf == buf_end);
+       *offset += count;
+       return count;
+}
+
 /* Page cache functions */
 
 /* Looks up the index'th page in the page map, returning an incref'd reference,