loff_t old_pos = 0;
        loff_t new_pos = 0;
        loff_t cloned;
+       loff_t data_pos = -1;
+       loff_t hole_len;
+       bool skip_hole = false;
        int error = 0;
 
        if (len == 0)
                goto out;
        /* Couldn't clone, so now we try to copy the data */
 
-       /* FIXME: copy up sparse files efficiently */
+       /* Check if lower fs supports seek operation */
+       if (old_file->f_mode & FMODE_LSEEK &&
+           old_file->f_op->llseek)
+               skip_hole = true;
+
        while (len) {
                size_t this_len = OVL_COPY_UP_CHUNK_SIZE;
                long bytes;
                        break;
                }
 
+               /*
+                * Fill zero for hole will cost unnecessary disk space
+                * and meanwhile slow down the copy-up speed, so we do
+                * an optimization for hole during copy-up, it relies
+                * on SEEK_DATA implementation in lower fs so if lower
+                * fs does not support it, copy-up will behave as before.
+                *
+                * Detail logic of hole detection as below:
+                * When we detect next data position is larger than current
+                * position we will skip that hole, otherwise we copy
+                * data in the size of OVL_COPY_UP_CHUNK_SIZE. Actually,
+                * it may not recognize all kind of holes and sometimes
+                * only skips partial of hole area. However, it will be
+                * enough for most of the use cases.
+                */
+
+               if (skip_hole && data_pos < old_pos) {
+                       data_pos = vfs_llseek(old_file, old_pos, SEEK_DATA);
+                       if (data_pos > old_pos) {
+                               hole_len = data_pos - old_pos;
+                               len -= hole_len;
+                               old_pos = new_pos = data_pos;
+                               continue;
+                       } else if (data_pos == -ENXIO) {
+                               break;
+                       } else if (data_pos < 0) {
+                               skip_hole = false;
+                       }
+               }
+
                bytes = do_splice_direct(old_file, &old_pos,
                                         new_file, &new_pos,
                                         this_len, SPLICE_F_MOVE);
        }
 
        inode_lock(temp->d_inode);
-       if (c->metacopy)
+       if (S_ISREG(c->stat.mode))
                err = ovl_set_size(temp, &c->stat);
        if (!err)
                err = ovl_set_attr(temp, &c->stat);