|
@@ -983,6 +983,96 @@ out:
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static int userfaultfd_copy(struct userfaultfd_ctx *ctx,
|
|
|
|
+ unsigned long arg)
|
|
|
|
+{
|
|
|
|
+ __s64 ret;
|
|
|
|
+ struct uffdio_copy uffdio_copy;
|
|
|
|
+ struct uffdio_copy __user *user_uffdio_copy;
|
|
|
|
+ struct userfaultfd_wake_range range;
|
|
|
|
+
|
|
|
|
+ user_uffdio_copy = (struct uffdio_copy __user *) arg;
|
|
|
|
+
|
|
|
|
+ ret = -EFAULT;
|
|
|
|
+ if (copy_from_user(&uffdio_copy, user_uffdio_copy,
|
|
|
|
+ /* don't copy "copy" last field */
|
|
|
|
+ sizeof(uffdio_copy)-sizeof(__s64)))
|
|
|
|
+ goto out;
|
|
|
|
+
|
|
|
|
+ ret = validate_range(ctx->mm, uffdio_copy.dst, uffdio_copy.len);
|
|
|
|
+ if (ret)
|
|
|
|
+ goto out;
|
|
|
|
+ /*
|
|
|
|
+ * double check for wraparound just in case. copy_from_user()
|
|
|
|
+ * will later check uffdio_copy.src + uffdio_copy.len to fit
|
|
|
|
+ * in the userland range.
|
|
|
|
+ */
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+ if (uffdio_copy.src + uffdio_copy.len <= uffdio_copy.src)
|
|
|
|
+ goto out;
|
|
|
|
+ if (uffdio_copy.mode & ~UFFDIO_COPY_MODE_DONTWAKE)
|
|
|
|
+ goto out;
|
|
|
|
+
|
|
|
|
+ ret = mcopy_atomic(ctx->mm, uffdio_copy.dst, uffdio_copy.src,
|
|
|
|
+ uffdio_copy.len);
|
|
|
|
+ if (unlikely(put_user(ret, &user_uffdio_copy->copy)))
|
|
|
|
+ return -EFAULT;
|
|
|
|
+ if (ret < 0)
|
|
|
|
+ goto out;
|
|
|
|
+ BUG_ON(!ret);
|
|
|
|
+ /* len == 0 would wake all */
|
|
|
|
+ range.len = ret;
|
|
|
|
+ if (!(uffdio_copy.mode & UFFDIO_COPY_MODE_DONTWAKE)) {
|
|
|
|
+ range.start = uffdio_copy.dst;
|
|
|
|
+ wake_userfault(ctx, &range);
|
|
|
|
+ }
|
|
|
|
+ ret = range.len == uffdio_copy.len ? 0 : -EAGAIN;
|
|
|
|
+out:
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int userfaultfd_zeropage(struct userfaultfd_ctx *ctx,
|
|
|
|
+ unsigned long arg)
|
|
|
|
+{
|
|
|
|
+ __s64 ret;
|
|
|
|
+ struct uffdio_zeropage uffdio_zeropage;
|
|
|
|
+ struct uffdio_zeropage __user *user_uffdio_zeropage;
|
|
|
|
+ struct userfaultfd_wake_range range;
|
|
|
|
+
|
|
|
|
+ user_uffdio_zeropage = (struct uffdio_zeropage __user *) arg;
|
|
|
|
+
|
|
|
|
+ ret = -EFAULT;
|
|
|
|
+ if (copy_from_user(&uffdio_zeropage, user_uffdio_zeropage,
|
|
|
|
+ /* don't copy "zeropage" last field */
|
|
|
|
+ sizeof(uffdio_zeropage)-sizeof(__s64)))
|
|
|
|
+ goto out;
|
|
|
|
+
|
|
|
|
+ ret = validate_range(ctx->mm, uffdio_zeropage.range.start,
|
|
|
|
+ uffdio_zeropage.range.len);
|
|
|
|
+ if (ret)
|
|
|
|
+ goto out;
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+ if (uffdio_zeropage.mode & ~UFFDIO_ZEROPAGE_MODE_DONTWAKE)
|
|
|
|
+ goto out;
|
|
|
|
+
|
|
|
|
+ ret = mfill_zeropage(ctx->mm, uffdio_zeropage.range.start,
|
|
|
|
+ uffdio_zeropage.range.len);
|
|
|
|
+ if (unlikely(put_user(ret, &user_uffdio_zeropage->zeropage)))
|
|
|
|
+ return -EFAULT;
|
|
|
|
+ if (ret < 0)
|
|
|
|
+ goto out;
|
|
|
|
+ /* len == 0 would wake all */
|
|
|
|
+ BUG_ON(!ret);
|
|
|
|
+ range.len = ret;
|
|
|
|
+ if (!(uffdio_zeropage.mode & UFFDIO_ZEROPAGE_MODE_DONTWAKE)) {
|
|
|
|
+ range.start = uffdio_zeropage.range.start;
|
|
|
|
+ wake_userfault(ctx, &range);
|
|
|
|
+ }
|
|
|
|
+ ret = range.len == uffdio_zeropage.range.len ? 0 : -EAGAIN;
|
|
|
|
+out:
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+
|
|
/*
|
|
/*
|
|
* userland asks for a certain API version and we return which bits
|
|
* userland asks for a certain API version and we return which bits
|
|
* and ioctl commands are implemented in this kernel for such API
|
|
* and ioctl commands are implemented in this kernel for such API
|
|
@@ -1038,6 +1128,12 @@ static long userfaultfd_ioctl(struct file *file, unsigned cmd,
|
|
case UFFDIO_WAKE:
|
|
case UFFDIO_WAKE:
|
|
ret = userfaultfd_wake(ctx, arg);
|
|
ret = userfaultfd_wake(ctx, arg);
|
|
break;
|
|
break;
|
|
|
|
+ case UFFDIO_COPY:
|
|
|
|
+ ret = userfaultfd_copy(ctx, arg);
|
|
|
|
+ break;
|
|
|
|
+ case UFFDIO_ZEROPAGE:
|
|
|
|
+ ret = userfaultfd_zeropage(ctx, arg);
|
|
|
|
+ break;
|
|
}
|
|
}
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|