changeset 787:1616d976e9da

Provider-specific copy option to test extensibility.
author alanb
date Sat, 25 Oct 2008 13:35:19 +0100
parents 9889b32ad574
children 03a74ce1bcc5
files make/java/nio/FILES_java.gmk src/share/classes/com/sun/nio/file/ExtendedCopyOption.java src/share/classes/sun/nio/fs/Cancellable.java src/solaris/classes/sun/nio/fs/SolarisNamedAttributeView.java src/solaris/classes/sun/nio/fs/UnixCopyFile.java src/solaris/native/sun/nio/fs/UnixCopyFile.c src/windows/classes/sun/nio/fs/WindowsFileCopy.java src/windows/classes/sun/nio/fs/WindowsNativeDispatcher.java src/windows/native/sun/nio/fs/WindowsNativeDispatcher.c test/java/nio/file/Path/InterruptCopy.java
diffstat 10 files changed, 510 insertions(+), 132 deletions(-) [+]
line wrap: on
line diff
--- a/make/java/nio/FILES_java.gmk	Sat Oct 25 13:20:39 2008 +0100
+++ b/make/java/nio/FILES_java.gmk	Sat Oct 25 13:35:19 2008 +0100
@@ -153,6 +153,7 @@
 	java/nio/file/spi/FileSystemProvider.java \
 	java/nio/file/spi/FileTypeDetector.java \
 	\
+	com/sun/nio/file/ExtendedCopyOption.java \
 	com/sun/nio/file/ExtendedOpenOption.java \
 	com/sun/nio/file/ExtendedWatchEventModifier.java \
 	\
@@ -249,6 +250,7 @@
 	sun/nio/fs/AbstractNamedAttributeView.java \
 	sun/nio/fs/AbstractWatchKey.java \
 	sun/nio/fs/AbstractWatchService.java \
+	sun/nio/fs/Cancellable.java \
 	sun/nio/fs/DefaultFileSystemProvider.java \
 	sun/nio/fs/DefaultFileTypeDetector.java \
 	sun/nio/fs/FileOwnerAttributeViewImpl.java \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/com/sun/nio/file/ExtendedCopyOption.java	Sat Oct 25 13:35:19 2008 +0100
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2007-2008 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.nio.file;
+
+import java.nio.file.CopyOption;
+
+/**
+ * Defines <em>extended</em> copy options supported on some platforms
+ * by Sun's provider implementation.
+ *
+ * @since 1.7
+ */
+
+public enum ExtendedCopyOption implements CopyOption {
+    /**
+     * The copy may be interrupted by the {@link Thread#interrupt interrupt}
+     * method.
+     */
+    INTERRUPTIBLE,
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/sun/nio/fs/Cancellable.java	Sat Oct 25 13:35:19 2008 +0100
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2007-2008 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.nio.fs;
+
+import sun.misc.Unsafe;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Base implementation of a task (typically native) that polls a memory location
+ * during execution so that it may be aborted/cancelled before completion. The
+ * task is executed by invoking the {@link runInterruptibly} method defined
+ * here and cancelled by invoking Thread.interrupt.
+ */
+
+abstract class Cancellable implements Runnable {
+    private static final Unsafe unsafe = Unsafe.getUnsafe();
+
+    private final long pollingAddress;
+    private final Object lock = new Object();
+
+    // the following require lock when examining or changing
+    private boolean completed;
+    private Throwable exception;
+
+    protected Cancellable() {
+        pollingAddress = unsafe.allocateMemory(4);
+        unsafe.putIntVolatile(null, pollingAddress, 0);
+    }
+
+    /**
+     * Returns the memory address of a 4-byte int that should be polled to
+     * detect cancellation.
+     */
+    protected long addressToPollForCancel() {
+        return pollingAddress;
+    }
+
+    /**
+     * The value to write to the polled memory location to indicate that the
+     * task has been cancelled. If this method is not overridden then it
+     * defaults to MAX_VALUE.
+     */
+    protected int cancelValue() {
+        return Integer.MAX_VALUE;
+    }
+
+    /**
+     * "cancels" the task by writing bits into memory location that it polled
+     * by the task.
+     */
+    final void cancel() {
+        synchronized (lock) {
+            if (!completed) {
+                unsafe.putIntVolatile(null, pollingAddress, cancelValue());
+            }
+        }
+    }
+
+    /**
+     * Returns the exception thrown by the task or null if the task completed
+     * successfully.
+     */
+    private Throwable exception() {
+        synchronized (lock) {
+            return exception;
+        }
+    }
+
+    @Override
+    public final void run() {
+        try {
+            implRun();
+        } catch (Throwable t) {
+            synchronized (lock) {
+                exception = t;
+            }
+        } finally {
+            synchronized (lock) {
+                completed = true;
+                unsafe.freeMemory(pollingAddress);
+            }
+        }
+    }
+
+    /**
+     * The task body. This should periodically poll the memory location
+     * to check for cancellation.
+     */
+    abstract void implRun() throws Throwable;
+
+    /**
+     * Invokes the task in its own thread. If this (meaning the current) thread
+     * is interrupted then an attempt is make to cancel the background task by
+     * writting bits into the memory location that it is polling. On return,
+     * the interrupt status for this thread has been cleared.
+     */
+    static void runInterruptibly(Cancellable task) throws ExecutionException {
+        Thread t = new Thread(task);
+        t.start();
+        while (t.isAlive()) {
+            try {
+                t.join();
+            } catch (InterruptedException e) {
+                task.cancel();
+            }
+        }
+        Throwable exc = task.exception();
+        if (exc != null)
+            throw new ExecutionException(exc);
+    }
+}
--- a/src/solaris/classes/sun/nio/fs/SolarisNamedAttributeView.java	Sat Oct 25 13:20:39 2008 +0100
+++ b/src/solaris/classes/sun/nio/fs/SolarisNamedAttributeView.java	Sat Oct 25 13:35:19 2008 +0100
@@ -282,7 +282,7 @@
             int dst = openat(nfd, name, (O_CREAT|O_WRONLY|O_TRUNC|O_XATTR),
                 UnixFileModeAttribute.ALL_PERMISSIONS);
             try {
-                UnixCopyFile.transfer(dst, src);
+                UnixCopyFile.transfer(dst, src, 0L);
             } finally {
                 close(dst);
             }
--- a/src/solaris/classes/sun/nio/fs/UnixCopyFile.java	Sat Oct 25 13:20:39 2008 +0100
+++ b/src/solaris/classes/sun/nio/fs/UnixCopyFile.java	Sat Oct 25 13:35:19 2008 +0100
@@ -29,6 +29,8 @@
 import java.io.IOException;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.util.concurrent.ExecutionException;
+import com.sun.nio.file.ExtendedCopyOption;
 
 import static sun.nio.fs.UnixNativeDispatcher.*;
 import static sun.nio.fs.UnixConstants.*;
@@ -41,16 +43,86 @@
 class UnixCopyFile {
     private UnixCopyFile() {  }
 
+    // The flags that control how a file is copied or moved
+    private static class Flags {
+        boolean replaceExisting;
+        boolean atomicMove;
+        boolean followLinks;
+        boolean interruptible;
+
+        // the attributes to copy
+        boolean copyBasicAttributes;
+        boolean copyPosixAttributes;
+        boolean copyNonPosixAttributes;
+
+        // flags that indicate if we should fail if attributes cannot be copied
+        boolean failIfUnableToCopyBasic;
+        boolean failIfUnableToCopyPosix;
+        boolean failIfUnableToCopyNonPosix;
+
+        static Flags fromCopyOptions(CopyOption... options) {
+            Flags flags = new Flags();
+            flags.followLinks = true;
+            for (CopyOption option: options) {
+                if (option == StandardCopyOption.REPLACE_EXISTING) {
+                    flags.replaceExisting = true;
+                    continue;
+                }
+                if (option == StandardCopyOption.NOFOLLOW_LINKS) {
+                    flags.followLinks = false;
+                    continue;
+                }
+                if (option == StandardCopyOption.COPY_ATTRIBUTES) {
+                    // copy all attributes but only fail if basic attributes
+                    // cannot be copied
+                    flags.copyBasicAttributes = true;
+                    flags.copyPosixAttributes = true;
+                    flags.copyNonPosixAttributes = true;
+                    flags.failIfUnableToCopyBasic = true;
+                    continue;
+                }
+                if (option == ExtendedCopyOption.INTERRUPTIBLE) {
+                    flags.interruptible = true;
+                    continue;
+                }
+                if (option == null)
+                    throw new NullPointerException();
+                throw new UnsupportedOperationException("Unsupported copy option");
+            }
+            return flags;
+        }
+
+        static Flags fromMoveOptions(CopyOption... options) {
+            Flags flags = new Flags();
+            for (CopyOption option: options) {
+                if (option == StandardCopyOption.ATOMIC_MOVE) {
+                    flags.atomicMove = true;
+                    continue;
+                }
+                if (option == StandardCopyOption.REPLACE_EXISTING) {
+                    flags.replaceExisting = true;
+                    continue;
+                }
+                if (option == null)
+                    throw new NullPointerException();
+                throw new UnsupportedOperationException("Unsupported copy option");
+            }
+
+            // a move requires that all attributes be copied but only fail if
+            // the basic attributes cannot be copied
+            flags.copyBasicAttributes = true;
+            flags.copyPosixAttributes = true;
+            flags.copyNonPosixAttributes = true;
+            flags.failIfUnableToCopyBasic = true;
+            return flags;
+        }
+    }
+
     // copy directory from source to target
     private static void copyDirectory(UnixPath source,
                                       UnixFileAttributes attrs,
                                       UnixPath target,
-                                      boolean copyBasicAttributes,
-                                      boolean failIfUnableToCopyBasic,
-                                      boolean copyPosixAttributes,
-                                      boolean failIfUnableToCopyPosix,
-                                      boolean copyNonPosixAttributes,
-                                      boolean failIfUnableToCopyNonPosix)
+                                      Flags flags)
         throws IOException
     {
         try {
@@ -60,8 +132,9 @@
         }
 
         // no attributes to copy
-        if (!copyBasicAttributes && !copyPosixAttributes && !copyNonPosixAttributes)
-            return;
+        if (!flags.copyBasicAttributes &&
+            !flags.copyPosixAttributes &&
+            !flags.copyNonPosixAttributes) return;
 
         // open target directory if possible (this can fail when copying a
         // directory for which we don't have read access).
@@ -70,7 +143,7 @@
             dfd = open(target, O_RDONLY, 0);
         } catch (UnixException x) {
             // access to target directory required to copy named attributes
-            if (copyNonPosixAttributes && failIfUnableToCopyNonPosix) {
+            if (flags.copyNonPosixAttributes && flags.failIfUnableToCopyNonPosix) {
                 try { rmdir(target); } catch (UnixException ignore) { }
                 x.rethrowAsIOException(target);
             }
@@ -79,7 +152,7 @@
         boolean done = false;
         try {
             // copy owner/group/permissions
-            if (copyPosixAttributes){
+            if (flags.copyPosixAttributes){
                 try {
                     if (dfd >= 0) {
                         fchown(dfd, attrs.uid(), attrs.gid());
@@ -90,17 +163,17 @@
                     }
                 } catch (UnixException x) {
                     // unable to set owner/group
-                    if (failIfUnableToCopyPosix)
+                    if (flags.failIfUnableToCopyPosix)
                         x.rethrowAsIOException(target);
                 }
             }
             // copy other attributes
-            if (copyNonPosixAttributes && (dfd >= 0)) {
+            if (flags.copyNonPosixAttributes && (dfd >= 0)) {
                 int sfd = -1;
                 try {
                     sfd = open(source, O_RDONLY, 0);
                 } catch (UnixException x) {
-                    if (failIfUnableToCopyNonPosix)
+                    if (flags.failIfUnableToCopyNonPosix)
                         x.rethrowAsIOException(source);
                 }
                 if (sfd >= 0) {
@@ -109,7 +182,7 @@
                 }
             }
             // copy time stamps last
-            if (copyBasicAttributes) {
+            if (flags.copyBasicAttributes) {
                 try {
                     if (dfd >= 0) {
                         futimes(dfd, attrs.lastAccessTime(),
@@ -120,7 +193,7 @@
                     }
                 } catch (UnixException x) {
                     // unable to set times
-                    if (failIfUnableToCopyBasic)
+                    if (flags.failIfUnableToCopyBasic)
                         x.rethrowAsIOException(target);
                 }
             }
@@ -139,12 +212,8 @@
     private static void copyFile(UnixPath source,
                                  UnixFileAttributes attrs,
                                  UnixPath  target,
-                                 boolean copyBasicAttributes,
-                                 boolean failIfUnableToCopyBasic,
-                                 boolean copyPosixAttributes,
-                                 boolean failIfUnableToCopyPosix,
-                                 boolean copyNonPosixAttributes,
-                                 boolean failIfUnableToCopyNonPosix)
+                                 Flags flags,
+                                 long addressToPollForCancel)
         throws IOException
     {
         int fi = -1;
@@ -172,30 +241,30 @@
             try {
                 // transfer bytes to target file
                 try {
-                    transfer(fo, fi);
+                    transfer(fo, fi, addressToPollForCancel);
                 } catch (UnixException x) {
                     x.rethrowAsIOException(source, target);
                 }
                 // copy owner/permissions
-                if (copyPosixAttributes) {
+                if (flags.copyPosixAttributes) {
                     try {
                         fchown(fo, attrs.uid(), attrs.gid());
                         fchmod(fo, attrs.mode());
                     } catch (UnixException x) {
-                        if (failIfUnableToCopyPosix)
+                        if (flags.failIfUnableToCopyPosix)
                             x.rethrowAsIOException(target);
                     }
                 }
                 // copy non POSIX attributes (depends on file system)
-                if (copyNonPosixAttributes) {
+                if (flags.copyNonPosixAttributes) {
                     source.getFileSystem().copyNonPosixAttributes(fi, fo);
                 }
                 // copy time attributes
-                if (copyBasicAttributes) {
+                if (flags.copyBasicAttributes) {
                     try {
                         futimes(fo, attrs.lastAccessTime(), attrs.lastModifiedTime());
                     } catch (UnixException x) {
-                        if (failIfUnableToCopyBasic)
+                        if (flags.failIfUnableToCopyBasic)
                             x.rethrowAsIOException(target);
                     }
                 }
@@ -219,7 +288,7 @@
     private static void copyLink(UnixPath source,
                                  UnixFileAttributes attrs,
                                  UnixPath  target,
-                                 boolean copyOwner)
+                                 Flags flags)
         throws IOException
     {
         byte[] linktarget = null;
@@ -231,7 +300,7 @@
         try {
             symlink(linktarget, target);
 
-            if (copyOwner) {
+            if (flags.copyPosixAttributes) {
                 try {
                     lchown(target, attrs.uid(), attrs.gid());
                 } catch (UnixException x) {
@@ -247,10 +316,7 @@
     private static void copySpecial(UnixPath source,
                                     UnixFileAttributes attrs,
                                     UnixPath  target,
-                                    boolean copyBasicAttributes,
-                                    boolean failIfUnableToCopyBasic,
-                                    boolean copyPosixAttributes,
-                                    boolean failIfUnableToCopyPosix)
+                                    Flags flags)
         throws IOException
     {
         try {
@@ -260,20 +326,20 @@
         }
         boolean done = false;
         try {
-            if (copyPosixAttributes) {
+            if (flags.copyPosixAttributes) {
                 try {
                     chown(target, attrs.uid(), attrs.gid());
                     chmod(target, attrs.mode());
                 } catch (UnixException x) {
-                    if (failIfUnableToCopyPosix)
+                    if (flags.failIfUnableToCopyPosix)
                         x.rethrowAsIOException(target);
                 }
             }
-            if (copyBasicAttributes) {
+            if (flags.copyBasicAttributes) {
                 try {
                     utimes(target, attrs.lastAccessTime(), attrs.lastModifiedTime());
                 } catch (UnixException x) {
-                    if (failIfUnableToCopyBasic)
+                    if (flags.failIfUnableToCopyBasic)
                         x.rethrowAsIOException(target);
                 }
             }
@@ -296,25 +362,11 @@
             target.checkWrite();
         }
 
-        // map options
-        boolean atomicMove = false;
-        boolean replaceExisting = false;
-        for (CopyOption option: options) {
-            if (option == StandardCopyOption.ATOMIC_MOVE) {
-                atomicMove = true;
-                continue;
-            }
-            if (option == StandardCopyOption.REPLACE_EXISTING) {
-                replaceExisting = true;
-                continue;
-            }
-            if (option == null)
-                throw new NullPointerException();
-            throw new UnsupportedOperationException("Unsupported copy option");
-        }
+        // translate options into flags
+        Flags flags = Flags.fromMoveOptions(options);
 
         // handle atomic rename case
-        if (atomicMove) {
+        if (flags.atomicMove) {
             try {
                 rename(source, target);
             } catch (UnixException x) {
@@ -355,7 +407,7 @@
         if (targetExists) {
             if (sourceAttrs.isSameFile(targetAttrs))
                 return;  // nothing to do as files are identical
-            if (!replaceExisting) {
+            if (!flags.replaceExisting) {
                 throw new FileAlreadyExistsException(
                     target.getPathForExecptionMessage());
             }
@@ -393,21 +445,15 @@
 
         // copy source to target
         if (sourceAttrs.isDirectory()) {
-            copyDirectory(source, sourceAttrs, target,
-                          true, true,     // copy basic attributes
-                          true, false,    // copy posix attributes
-                          true, false);   // copy non-posix attributes
+            copyDirectory(source, sourceAttrs, target, flags);
         } else {
             if (sourceAttrs.isSymbolicLink()) {
-                copyLink(source, sourceAttrs, target, true);
+                copyLink(source, sourceAttrs, target, flags);
             } else {
                 if (sourceAttrs.isDevice()) {
-                    copySpecial(source, sourceAttrs, target, true, true, true, false);
+                    copySpecial(source, sourceAttrs, target, flags);
                 } else {
-                    copyFile(source, sourceAttrs, target,
-                          true, true,     // copy basic attributes
-                          true, false,    // copy posix attributes
-                          true, false);   // copy non-posix attributes
+                    copyFile(source, sourceAttrs, target, flags, 0L);
                 }
             }
         }
@@ -441,8 +487,9 @@
     }
 
     // copy file from source to target
-    static void copy(UnixPath source, UnixPath target, CopyOption... options)
-        throws IOException
+    static void copy(final UnixPath source,
+                     final UnixPath target,
+                     CopyOption... options) throws IOException
     {
         // permission checks
         SecurityManager sm = System.getSecurityManager();
@@ -451,44 +498,15 @@
             target.checkWrite();
         }
 
-        // map options
-        boolean replaceExisting = false;
-        boolean copyBasicAttributes = false;
-        boolean failIfUnableToCopyBasic = false;
-        boolean copyPosixAttributes = false;
-        boolean failIfUnableToCopyPosix = false;
-        boolean copyNamedAttributes = false;
-        boolean failIfUnableToCopyNamed = false;
-
-        boolean followLinks = true;
-
-        for (CopyOption option: options) {
-            if (option == StandardCopyOption.REPLACE_EXISTING) {
-                replaceExisting = true;
-                continue;
-            }
-            if (option == StandardCopyOption.NOFOLLOW_LINKS) {
-                followLinks = false;
-                continue;
-            }
-            if (option == StandardCopyOption.COPY_ATTRIBUTES) {
-                copyBasicAttributes = true;
-                failIfUnableToCopyBasic = true;
-                copyPosixAttributes = true;
-                copyNamedAttributes = true;
-                continue;
-            }
-            if (option == null)
-                throw new NullPointerException();
-            throw new UnsupportedOperationException("Unsupported copy option");
-        }
+        // translate options into flags
+        final Flags flags = Flags.fromCopyOptions(options);
 
         UnixFileAttributes sourceAttrs = null;
         UnixFileAttributes targetAttrs = null;
 
         // get attributes of source file
         try {
-            sourceAttrs = UnixFileAttributes.get(source, followLinks);
+            sourceAttrs = UnixFileAttributes.get(source, flags.followLinks);
         } catch (UnixException x) {
             x.rethrowAsIOException(source);
         }
@@ -513,7 +531,7 @@
         if (targetExists) {
             if (sourceAttrs.isSameFile(targetAttrs))
                 return;  // nothing to do as files are identical
-            if (!replaceExisting)
+            if (!flags.replaceExisting)
                 throw new FileAlreadyExistsException(
                     target.getPathForExecptionMessage());
             try {
@@ -538,25 +556,41 @@
 
         // do the copy
         if (sourceAttrs.isDirectory()) {
-            copyDirectory(source, sourceAttrs, target,
-                copyBasicAttributes, failIfUnableToCopyBasic,
-                copyPosixAttributes, failIfUnableToCopyPosix,
-                copyNamedAttributes, failIfUnableToCopyNamed);
-        } else {
-            if (sourceAttrs.isSymbolicLink()) {
-                copyLink(source, sourceAttrs, target, copyPosixAttributes);
-            } else {
-                copyFile(source, sourceAttrs, target,
-                    copyBasicAttributes, failIfUnableToCopyBasic,
-                    copyPosixAttributes, failIfUnableToCopyPosix,
-                    copyNamedAttributes, failIfUnableToCopyNamed);
+            copyDirectory(source, sourceAttrs, target, flags);
+            return;
+        }
+        if (sourceAttrs.isSymbolicLink()) {
+            copyLink(source, sourceAttrs, target, flags);
+            return;
+        }
+        if (!flags.interruptible) {
+            // non-interruptible file copy
+            copyFile(source, sourceAttrs, target, flags, 0L);
+            return;
+        }
+
+        // interruptible file copy
+        final UnixFileAttributes attrsToCopy = sourceAttrs;
+        Cancellable copyTask = new Cancellable() {
+            @Override public void implRun() throws IOException {
+                copyFile(source, attrsToCopy, target, flags,
+                    addressToPollForCancel());
             }
+        };
+        try {
+            Cancellable.runInterruptibly(copyTask);
+        } catch (ExecutionException e) {
+            Throwable t = e.getCause();
+            if (t instanceof IOException)
+                throw (IOException)t;
+            throw new IOException(t);
         }
     }
 
     // -- native methods --
 
-    static native void transfer(int dst, int src) throws UnixException;
+    static native void transfer(int dst, int src, long addressToPollForCancel)
+        throws UnixException;
 
     static {
         AccessController.doPrivileged(new PrivilegedAction<Void>() {
--- a/src/solaris/native/sun/nio/fs/UnixCopyFile.c	Sat Oct 25 13:20:39 2008 +0100
+++ b/src/solaris/native/sun/nio/fs/UnixCopyFile.c	Sat Oct 25 13:35:19 2008 +0100
@@ -25,9 +25,10 @@
 
 #include "jni.h"
 #include "jni_util.h"
-#include "jvm.h"
+#include "jlong.h"
 
 #include <unistd.h>
+#include <errno.h>
 
 #include "sun_nio_fs_UnixCopyFile.h"
 
@@ -50,9 +51,10 @@
  */
 JNIEXPORT void JNICALL
 Java_sun_nio_fs_UnixCopyFile_transfer
-    (JNIEnv* env, jclass this, jint dst, jint src)
+    (JNIEnv* env, jclass this, jint dst, jint src, jlong cancelAddress)
 {
     char buf[8192];
+    volatile jint* cancel = (jint*)jlong_to_ptr(cancelAddress);
 
     for (;;) {
         ssize_t n, pos, len;
@@ -62,6 +64,10 @@
                 throwUnixException(env, errno);
             return;
         }
+        if (cancel != NULL && *cancel != 0) {
+            throwUnixException(env, ECANCELED);
+            return;
+        }
         pos = 0;
         len = n;
         do {
--- a/src/windows/classes/sun/nio/fs/WindowsFileCopy.java	Sat Oct 25 13:20:39 2008 +0100
+++ b/src/windows/classes/sun/nio/fs/WindowsFileCopy.java	Sat Oct 25 13:35:19 2008 +0100
@@ -26,8 +26,9 @@
 package sun.nio.fs;
 
 import java.nio.file.*;
-import java.nio.file.attribute.*;
 import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import com.sun.nio.file.ExtendedCopyOption;
 
 import static sun.nio.fs.WindowsNativeDispatcher.*;
 import static sun.nio.fs.WindowsConstants.*;
@@ -43,13 +44,16 @@
     /**
      * Copy file from source to target
      */
-    static void copy(WindowsPath source, WindowsPath target, CopyOption... options)
+    static void copy(final WindowsPath source,
+                     final WindowsPath target,
+                     CopyOption... options)
         throws IOException
     {
         // map options
         boolean replaceExisting = false;
         boolean copyAttributes = false;
         boolean followLinks = true;
+        boolean interruptible = false;
         for (CopyOption option: options) {
             if (option == StandardCopyOption.REPLACE_EXISTING) {
                 replaceExisting = true;
@@ -63,6 +67,10 @@
                 copyAttributes = true;
                 continue;
             }
+            if (option == ExtendedCopyOption.INTERRUPTIBLE) {
+                interruptible = true;
+                continue;
+            }
             if (option == null)
                 throw new NullPointerException();
             throw new UnsupportedOperationException("Unsupported copy option");
@@ -136,8 +144,8 @@
             sm.checkPermission(new LinkPermission("symbolic"));
         }
 
-        String sourcePath = source.getPathForWin32Calls();
-        String targetPath = target.getPathForWin32Calls();
+        final String sourcePath = source.getPathForWin32Calls();
+        final String targetPath = target.getPathForWin32Calls();
 
         // if target exists then delete it.
         if (targetAttrs != null) {
@@ -164,13 +172,42 @@
 
         // Use CopyFileEx if the file is not a directory or junction
         if (!sourceAttrs.isDirectory() && !sourceAttrs.isDirectoryLink()) {
-            int flags = 0;
-            if (source.getFileSystem().supportsLinks() && !followLinks)
-                flags |= COPY_FILE_COPY_SYMLINK;
-            try {
-                CopyFileEx(sourcePath, targetPath, flags);
-            } catch (WindowsException x) {
-                x.rethrowAsIOException(source, target);
+            final int flags =
+                (source.getFileSystem().supportsLinks() && !followLinks) ?
+                COPY_FILE_COPY_SYMLINK : 0;
+
+            if (interruptible) {
+                // interruptible copy
+                Cancellable copyTask = new Cancellable() {
+                    @Override
+                    public int cancelValue() {
+                        return 1;  // TRUE
+                    }
+                    @Override
+                    public void implRun() throws IOException {
+                        try {
+                            CopyFileEx(sourcePath, targetPath, flags,
+                                       addressToPollForCancel());
+                        } catch (WindowsException x) {
+                            x.rethrowAsIOException(source, target);
+                        }
+                    }
+                };
+                try {
+                    Cancellable.runInterruptibly(copyTask);
+                } catch (ExecutionException e) {
+                    Throwable t = e.getCause();
+                    if (t instanceof IOException)
+                        throw (IOException)t;
+                    throw new IOException(t);
+                }
+            } else {
+                // non-interruptible copy
+                try {
+                    CopyFileEx(sourcePath, targetPath, flags, 0L);
+                } catch (WindowsException x) {
+                    x.rethrowAsIOException(source, target);
+                }
             }
             if (copyAttributes) {
                 // CopyFileEx does not copy security attributes
--- a/src/windows/classes/sun/nio/fs/WindowsNativeDispatcher.java	Sat Oct 25 13:20:39 2008 +0100
+++ b/src/windows/classes/sun/nio/fs/WindowsNativeDispatcher.java	Sat Oct 25 13:35:19 2008 +0100
@@ -266,20 +266,22 @@
      *   DWORD dwCopyFlags
      * )
      */
-    static void CopyFileEx(String source, String target, int flags)
+    static void CopyFileEx(String source, String target, int flags,
+                           long addressToPollForCancel)
         throws WindowsException
     {
         NativeBuffer sourceBuffer = asNativeBuffer(source);
         NativeBuffer targetBuffer = asNativeBuffer(target);
         try {
-            CopyFileEx0(sourceBuffer.address(), targetBuffer.address(), flags);
+            CopyFileEx0(sourceBuffer.address(), targetBuffer.address(), flags,
+                        addressToPollForCancel);
         } finally {
             targetBuffer.release();
             sourceBuffer.release();
         }
     }
     private static native void CopyFileEx0(long existingAddress, long newAddress,
-        int flags) throws WindowsException;
+        int flags, long addressToPollForCancel) throws WindowsException;
 
     /**
      * MoveFileEx(
--- a/src/windows/native/sun/nio/fs/WindowsNativeDispatcher.c	Sat Oct 25 13:20:39 2008 +0100
+++ b/src/windows/native/sun/nio/fs/WindowsNativeDispatcher.c	Sat Oct 25 13:35:19 2008 +0100
@@ -477,11 +477,14 @@
 
 JNIEXPORT void JNICALL
 Java_sun_nio_fs_WindowsNativeDispatcher_CopyFileEx0(JNIEnv* env, jclass this,
-    jlong existingAddress, jlong newAddress, jint flags)
+    jlong existingAddress, jlong newAddress, jint flags, jlong cancelAddress)
 {
     LPCWSTR lpExistingFileName = jlong_to_ptr(existingAddress);
     LPCWSTR lpNewFileName = jlong_to_ptr(newAddress);
-    if (CopyFileExW(lpExistingFileName, lpNewFileName, NULL, NULL, FALSE, (DWORD)flags) == 0) {
+    LPBOOL cancel = (LPBOOL)jlong_to_ptr(cancelAddress);
+    if (CopyFileExW(lpExistingFileName, lpNewFileName, NULL, NULL, cancel,
+                    (DWORD)flags) == 0)
+    {
         throwWindowsException(env, GetLastError());
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/nio/file/Path/InterruptCopy.java	Sat Oct 25 13:35:19 2008 +0100
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2007-2008 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/* @test
+ * @bug 4313887
+ * @summary Unit test for Sun-specific ExtendedCopyOption.INTERRUPTIBLE option
+ * @library ..
+ * @run main/othervm -XX:-UseVMInterruptibleIO InterruptCopy
+ */
+
+import java.nio.file.*;
+import java.nio.file.attribute.Attributes;
+import java.io.*;
+import java.util.concurrent.*;
+import com.sun.nio.file.ExtendedCopyOption;
+
+public class InterruptCopy {
+
+    private static final long FILE_SIZE_TO_COPY = 512 * 1024 * 1024;
+    private static final int DELAY_IN_MS = 500;
+
+    public static void main(String[] args) throws Exception {
+        Path dir = TestUtil.createTemporaryDirectory();
+        try {
+            FileStore store = dir.getFileStore();
+            System.out.format("Checking space (%s)\n", store);
+            long usableSpace = Attributes
+                .readFileStoreSpaceAttributes(store).usableSpace();
+            if (usableSpace < 2*FILE_SIZE_TO_COPY) {
+                System.out.println("Insufficient disk space to run test.");
+                return;
+            }
+            doTest(dir);
+        } finally {
+            TestUtil.removeAll(dir);
+        }
+    }
+
+    static void doTest(Path dir) throws Exception {
+        final Path source = dir.resolve("foo");
+        final Path target = dir.resolve("bar");
+
+        // create source file (don't create it as sparse file because we
+        // require the copy to take a long time)
+        System.out.println("Creating source file...");
+        byte[] buf = new byte[32*1024];
+        long total = 0;
+        OutputStream out = source.newOutputStream();
+        try {
+            do {
+                out.write(buf);
+                total += buf.length;
+            } while (total < FILE_SIZE_TO_COPY);
+        } finally {
+            out.close();
+        }
+        System.out.println("Source file created.");
+
+        ScheduledExecutorService pool =
+            Executors.newSingleThreadScheduledExecutor();
+        try {
+            // copy source to target in main thread, interrupting it after a delay
+            final Thread me = Thread.currentThread();
+            pool.schedule(new Runnable() {
+                public void run() {
+                    me.interrupt();
+                }}, DELAY_IN_MS, TimeUnit.MILLISECONDS);
+            System.out.println("Copying file...");
+            try {
+                source.copyTo(target, ExtendedCopyOption.INTERRUPTIBLE);
+                throw new RuntimeException("Copy completed (this is not expected)");
+            } catch (IOException e) {
+                System.out.println("Copy failed (this is expected)");
+                e.printStackTrace();
+            }
+
+            // copy source to target via task in thread pool, interrupting it after
+            // a delay using cancel(true)
+            Future<Void> result = pool.submit(new Callable<Void>() {
+                public Void call() throws IOException {
+                    System.out.println("Copying file...");
+                    source.copyTo(target, ExtendedCopyOption.INTERRUPTIBLE,
+                        StandardCopyOption.REPLACE_EXISTING);
+                    return null;
+                }
+            });
+            Thread.sleep(DELAY_IN_MS);
+            boolean cancelled = result.cancel(true);
+            if (!cancelled)
+                result.get();
+            System.out.println("Copy cancelled.");
+        } finally {
+            pool.shutdown();
+            pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
+        }
+    }
+}