ceph: fix reading inline data when i_size > PAGE_SIZE
authorYan, Zheng <zyan@redhat.com>
Sat, 10 Jan 2015 03:43:12 +0000 (11:43 +0800)
committerIlya Dryomov <idryomov@gmail.com>
Thu, 19 Feb 2015 10:31:39 +0000 (13:31 +0300)
when inode has inline data but its size > PAGE_SIZE (it was truncated
to larger size), previous direct read code return -EIO. This patch adds
code to return zeros for data whose offset > PAGE_SIZE.

Signed-off-by: Yan, Zheng <zyan@redhat.com>
fs/ceph/addr.c
fs/ceph/file.c

index c81c0e004588b9e2ae03580ed6265096d6608d46..7d05e37874d456d66c0edf41b0d790fba9cf3331 100644 (file)
@@ -196,17 +196,22 @@ static int readpage_nounlock(struct file *filp, struct page *page)
        u64 len = PAGE_CACHE_SIZE;
 
        if (off >= i_size_read(inode)) {
-               zero_user_segment(page, err, PAGE_CACHE_SIZE);
+               zero_user_segment(page, 0, PAGE_CACHE_SIZE);
                SetPageUptodate(page);
                return 0;
        }
 
-       /*
-        * Uptodate inline data should have been added into page cache
-        * while getting Fcr caps.
-        */
-       if (ci->i_inline_version != CEPH_INLINE_NONE)
-               return -EINVAL;
+       if (ci->i_inline_version != CEPH_INLINE_NONE) {
+               /*
+                * Uptodate inline data should have been added
+                * into page cache while getting Fcr caps.
+                */
+               if (off == 0)
+                       return -EINVAL;
+               zero_user_segment(page, 0, PAGE_CACHE_SIZE);
+               SetPageUptodate(page);
+               return 0;
+       }
 
        err = ceph_readpage_from_fscache(inode, page);
        if (err == 0)
index 663da44c06b6053e83bd7712622c798dfaf750c5..c407abb52b7be8d23f74067ede7bd9eea3551bae 100644 (file)
@@ -879,28 +879,34 @@ again:
 
                i_size = i_size_read(inode);
                if (retry_op == READ_INLINE) {
-                       /* does not support inline data > PAGE_SIZE */
-                       if (i_size > PAGE_CACHE_SIZE) {
-                               ret = -EIO;
-                       } else if (iocb->ki_pos < i_size) {
+                       BUG_ON(ret > 0 || read > 0);
+                       if (iocb->ki_pos < i_size &&
+                           iocb->ki_pos < PAGE_CACHE_SIZE) {
                                loff_t end = min_t(loff_t, i_size,
                                                   iocb->ki_pos + len);
+                               end = min_t(loff_t, end, PAGE_CACHE_SIZE);
                                if (statret < end)
                                        zero_user_segment(page, statret, end);
                                ret = copy_page_to_iter(page,
                                                iocb->ki_pos & ~PAGE_MASK,
                                                end - iocb->ki_pos, to);
                                iocb->ki_pos += ret;
-                       } else {
-                               ret = 0;
+                               read += ret;
+                       }
+                       if (iocb->ki_pos < i_size && read < len) {
+                               size_t zlen = min_t(size_t, len - read,
+                                                   i_size - iocb->ki_pos);
+                               ret = iov_iter_zero(zlen, to);
+                               iocb->ki_pos += ret;
+                               read += ret;
                        }
                        __free_pages(page, 0);
-                       return ret;
+                       return read;
                }
 
                /* hit EOF or hole? */
                if (retry_op == CHECK_EOF && iocb->ki_pos < i_size &&
-                       ret < len) {
+                   ret < len) {
                        dout("sync_read hit hole, ppos %lld < size %lld"
                             ", reading more\n", iocb->ki_pos,
                             inode->i_size);