1 /*
   2  * Copyright (c) 2008, 2009, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.nio.fs;
  27 
  28 import java.nio.file.*;
  29 import java.io.IOException;
  30 import java.util.concurrent.ExecutionException;
  31 import com.sun.nio.file.ExtendedCopyOption;
  32 
  33 import static sun.nio.fs.WindowsNativeDispatcher.*;
  34 import static sun.nio.fs.WindowsConstants.*;
  35 
  36 /**
  37  * Utility methods for copying and moving files.
  38  */
  39 
  40 class WindowsFileCopy {
  41     private WindowsFileCopy() {
  42     }
  43 
  44     /**
  45      * Copy file from source to target
  46      */
  47     static void copy(final WindowsPath source,
  48                      final WindowsPath target,
  49                      CopyOption... options)
  50         throws IOException
  51     {
  52         // map options
  53         boolean replaceExisting = false;
  54         boolean copyAttributes = false;
  55         boolean followLinks = true;
  56         boolean interruptible = false;
  57         for (CopyOption option: options) {
  58             if (option == StandardCopyOption.REPLACE_EXISTING) {
  59                 replaceExisting = true;
  60                 continue;
  61             }
  62             if (option == LinkOption.NOFOLLOW_LINKS) {
  63                 followLinks = false;
  64                 continue;
  65             }
  66             if (option == StandardCopyOption.COPY_ATTRIBUTES) {
  67                 copyAttributes = true;
  68                 continue;
  69             }
  70             if (option == ExtendedCopyOption.INTERRUPTIBLE) {
  71                 interruptible = true;
  72                 continue;
  73             }
  74             if (option == null)
  75                 throw new NullPointerException();
  76             throw new UnsupportedOperationException("Unsupported copy option");
  77         }
  78 
  79         // check permissions. If the source file is a symbolic link then
  80         // later we must also check LinkPermission
  81         SecurityManager sm = System.getSecurityManager();
  82         if (sm != null) {
  83             source.checkRead();
  84             target.checkWrite();
  85         }
  86 
  87         // get attributes of source file
  88         // attempt to get attributes of target file
  89         // if both files are the same there is nothing to do
  90         // if target exists and !replace then throw exception
  91 
  92         WindowsFileAttributes sourceAttrs = null;
  93         WindowsFileAttributes targetAttrs = null;
  94 
  95         long sourceHandle = 0L;
  96         try {
  97             sourceHandle = source.openForReadAttributeAccess(followLinks);
  98         } catch (WindowsException x) {
  99             x.rethrowAsIOException(source);
 100         }
 101         try {
 102             // source attributes
 103             try {
 104                 sourceAttrs = WindowsFileAttributes.readAttributes(sourceHandle);
 105             } catch (WindowsException x) {
 106                 x.rethrowAsIOException(source);
 107             }
 108 
 109             // open target (don't follow links)
 110             long targetHandle = 0L;
 111             try {
 112                 targetHandle = target.openForReadAttributeAccess(false);
 113                 try {
 114                     targetAttrs = WindowsFileAttributes.readAttributes(targetHandle);
 115 
 116                     // if both files are the same then nothing to do
 117                     if (WindowsFileAttributes.isSameFile(sourceAttrs, targetAttrs)) {
 118                         return;
 119                     }
 120 
 121                     // can't replace file
 122                     if (!replaceExisting) {
 123                         throw new FileAlreadyExistsException(
 124                             target.getPathForExceptionMessage());
 125                     }
 126 
 127                 } finally {
 128                     CloseHandle(targetHandle);
 129                 }
 130             } catch (WindowsException x) {
 131                 // ignore
 132             }
 133 
 134         } finally {
 135             CloseHandle(sourceHandle);
 136         }
 137 
 138         // if source file is a symbolic link then we must check for LinkPermission
 139         if (sm != null && sourceAttrs.isSymbolicLink()) {
 140             sm.checkPermission(new LinkPermission("symbolic"));
 141         }
 142 
 143         final String sourcePath = asWin32Path(source);
 144         final String targetPath = asWin32Path(target);
 145 
 146         // if target exists then delete it.
 147         if (targetAttrs != null) {
 148             try {
 149                 if (targetAttrs.isDirectory() || targetAttrs.isDirectoryLink()) {
 150                     RemoveDirectory(targetPath);
 151                 } else {
 152                     DeleteFile(targetPath);
 153                 }
 154             } catch (WindowsException x) {
 155                 if (targetAttrs.isDirectory()) {
 156                     // ERROR_ALREADY_EXISTS is returned when attempting to delete
 157                     // non-empty directory on SAMBA servers.
 158                     if (x.lastError() == ERROR_DIR_NOT_EMPTY ||
 159                         x.lastError() == ERROR_ALREADY_EXISTS)
 160                     {
 161                         throw new DirectoryNotEmptyException(
 162                             target.getPathForExceptionMessage());
 163                     }
 164                 }
 165                 x.rethrowAsIOException(target);
 166             }
 167         }
 168 
 169         // Use CopyFileEx if the file is not a directory or junction
 170         if (!sourceAttrs.isDirectory() && !sourceAttrs.isDirectoryLink()) {
 171             final int flags =
 172                 (source.getFileSystem().supportsLinks() && !followLinks) ?
 173                 COPY_FILE_COPY_SYMLINK : 0;
 174 
 175             if (interruptible) {
 176                 // interruptible copy
 177                 Cancellable copyTask = new Cancellable() {
 178                     @Override
 179                     public int cancelValue() {
 180                         return 1;  // TRUE
 181                     }
 182                     @Override
 183                     public void implRun() throws IOException {
 184                         try {
 185                             CopyFileEx(sourcePath, targetPath, flags,
 186                                        addressToPollForCancel());
 187                         } catch (WindowsException x) {
 188                             x.rethrowAsIOException(source, target);
 189                         }
 190                     }
 191                 };
 192                 try {
 193                     Cancellable.runInterruptibly(copyTask);
 194                 } catch (ExecutionException e) {
 195                     Throwable t = e.getCause();
 196                     if (t instanceof IOException)
 197                         throw (IOException)t;
 198                     throw new IOException(t);
 199                 }
 200             } else {
 201                 // non-interruptible copy
 202                 try {
 203                     CopyFileEx(sourcePath, targetPath, flags, 0L);
 204                 } catch (WindowsException x) {
 205                     x.rethrowAsIOException(source, target);
 206                 }
 207             }
 208             if (copyAttributes) {
 209                 // CopyFileEx does not copy security attributes
 210                 try {
 211                     copySecurityAttributes(source, target, followLinks);
 212                 } catch (IOException x) {
 213                     // ignore
 214                 }
 215             }
 216             return;
 217         }
 218 
 219         // copy directory or directory junction
 220         try {
 221             if (sourceAttrs.isDirectory()) {
 222                 CreateDirectory(targetPath, 0L);
 223             } else {
 224                 String linkTarget = WindowsLinkSupport.readLink(source);
 225                 int flags = SYMBOLIC_LINK_FLAG_DIRECTORY;
 226                 CreateSymbolicLink(targetPath,
 227                                    addPrefixIfNeeded(linkTarget),
 228                                    flags);
 229             }
 230         } catch (WindowsException x) {
 231             x.rethrowAsIOException(target);
 232         }
 233         if (copyAttributes) {
 234             // copy DOS/timestamps attributes
 235             WindowsFileAttributeViews.Dos view =
 236                 WindowsFileAttributeViews.createDosView(target, false);
 237             try {
 238                 view.setAttributes(sourceAttrs);
 239             } catch (IOException x) {
 240                 if (sourceAttrs.isDirectory()) {
 241                     try {
 242                         RemoveDirectory(targetPath);
 243                     } catch (WindowsException ignore) { }
 244                 }
 245             }
 246 
 247             // copy security attributes. If this fail it doesn't cause the move
 248             // to fail.
 249             try {
 250                 copySecurityAttributes(source, target, followLinks);
 251             } catch (IOException ignore) { }
 252         }
 253     }
 254 
 255     /**
 256      * Move file from source to target
 257      */
 258     static void move(WindowsPath source, WindowsPath target, CopyOption... options)
 259         throws IOException
 260     {
 261         // map options
 262         boolean atomicMove = false;
 263         boolean replaceExisting = false;
 264         for (CopyOption option: options) {
 265             if (option == StandardCopyOption.ATOMIC_MOVE) {
 266                 atomicMove = true;
 267                 continue;
 268             }
 269             if (option == StandardCopyOption.REPLACE_EXISTING) {
 270                 replaceExisting = true;
 271                 continue;
 272             }
 273             if (option == LinkOption.NOFOLLOW_LINKS) {
 274                 // ignore
 275                 continue;
 276             }
 277             if (option == null) throw new NullPointerException();
 278             throw new UnsupportedOperationException("Unsupported copy option");
 279         }
 280 
 281         SecurityManager sm = System.getSecurityManager();
 282         if (sm != null) {
 283             source.checkWrite();
 284             target.checkWrite();
 285         }
 286 
 287         final String sourcePath = asWin32Path(source);
 288         final String targetPath = asWin32Path(target);
 289 
 290         // atomic case
 291         if (atomicMove) {
 292             try {
 293                 MoveFileEx(sourcePath, targetPath, MOVEFILE_REPLACE_EXISTING);
 294             } catch (WindowsException x) {
 295                 if (x.lastError() == ERROR_NOT_SAME_DEVICE) {
 296                     throw new AtomicMoveNotSupportedException(
 297                         source.getPathForExceptionMessage(),
 298                         target.getPathForExceptionMessage(),
 299                         x.errorString());
 300                 }
 301                 x.rethrowAsIOException(source, target);
 302             }
 303             return;
 304         }
 305 
 306         // get attributes of source file
 307         // attempt to get attributes of target file
 308         // if both files are the same there is nothing to do
 309         // if target exists and !replace then throw exception
 310 
 311         WindowsFileAttributes sourceAttrs = null;
 312         WindowsFileAttributes targetAttrs = null;
 313 
 314         long sourceHandle = 0L;
 315         try {
 316             sourceHandle = source.openForReadAttributeAccess(false);
 317         } catch (WindowsException x) {
 318             x.rethrowAsIOException(source);
 319         }
 320         try {
 321             // source attributes
 322             try {
 323                 sourceAttrs = WindowsFileAttributes.readAttributes(sourceHandle);
 324             } catch (WindowsException x) {
 325                 x.rethrowAsIOException(source);
 326             }
 327 
 328             // open target (don't follow links)
 329             long targetHandle = 0L;
 330             try {
 331                 targetHandle = target.openForReadAttributeAccess(false);
 332                 try {
 333                     targetAttrs = WindowsFileAttributes.readAttributes(targetHandle);
 334 
 335                     // if both files are the same then nothing to do
 336                     if (WindowsFileAttributes.isSameFile(sourceAttrs, targetAttrs)) {
 337                         return;
 338                     }
 339 
 340                     // can't replace file
 341                     if (!replaceExisting) {
 342                         throw new FileAlreadyExistsException(
 343                             target.getPathForExceptionMessage());
 344                     }
 345 
 346                 } finally {
 347                     CloseHandle(targetHandle);
 348                 }
 349             } catch (WindowsException x) {
 350                 // ignore
 351             }
 352 
 353         } finally {
 354             CloseHandle(sourceHandle);
 355         }
 356 
 357         // if target exists then delete it.
 358         if (targetAttrs != null) {
 359             try {
 360                 if (targetAttrs.isDirectory() || targetAttrs.isDirectoryLink()) {
 361                     RemoveDirectory(targetPath);
 362                 } else {
 363                     DeleteFile(targetPath);
 364                 }
 365             } catch (WindowsException x) {
 366                 if (targetAttrs.isDirectory()) {
 367                     // ERROR_ALREADY_EXISTS is returned when attempting to delete
 368                     // non-empty directory on SAMBA servers.
 369                     if (x.lastError() == ERROR_DIR_NOT_EMPTY ||
 370                         x.lastError() == ERROR_ALREADY_EXISTS)
 371                     {
 372                         throw new DirectoryNotEmptyException(
 373                             target.getPathForExceptionMessage());
 374                     }
 375                 }
 376                 x.rethrowAsIOException(target);
 377             }
 378         }
 379 
 380         // first try MoveFileEx (no options). If target is on same volume then
 381         // all attributes (including security attributes) are preserved.
 382         try {
 383             MoveFileEx(sourcePath, targetPath, 0);
 384             return;
 385         } catch (WindowsException x) {
 386             if (x.lastError() != ERROR_NOT_SAME_DEVICE)
 387                 x.rethrowAsIOException(source, target);
 388         }
 389 
 390         // target is on different volume so use MoveFileEx with copy option
 391         if (!sourceAttrs.isDirectory() && !sourceAttrs.isDirectoryLink()) {
 392             try {
 393                 MoveFileEx(sourcePath, targetPath, MOVEFILE_COPY_ALLOWED);
 394             } catch (WindowsException x) {
 395                 x.rethrowAsIOException(source, target);
 396             }
 397             // MoveFileEx does not copy security attributes when moving
 398             // across volumes.
 399             try {
 400                 copySecurityAttributes(source, target, false);
 401             } catch (IOException x) {
 402                 // ignore
 403             }
 404             return;
 405         }
 406 
 407         // moving directory or directory-link to another file system
 408         assert sourceAttrs.isDirectory() || sourceAttrs.isDirectoryLink();
 409 
 410         // create new directory or directory junction
 411         try {
 412             if (sourceAttrs.isDirectory()) {
 413                 CreateDirectory(targetPath, 0L);
 414             } else {
 415                 String linkTarget = WindowsLinkSupport.readLink(source);
 416                 CreateSymbolicLink(targetPath,
 417                                    addPrefixIfNeeded(linkTarget),
 418                                    SYMBOLIC_LINK_FLAG_DIRECTORY);
 419             }
 420         } catch (WindowsException x) {
 421             x.rethrowAsIOException(target);
 422         }
 423 
 424         // copy timestamps/DOS attributes
 425         WindowsFileAttributeViews.Dos view =
 426                 WindowsFileAttributeViews.createDosView(target, false);
 427         try {
 428             view.setAttributes(sourceAttrs);
 429         } catch (IOException x) {
 430             // rollback
 431             try {
 432                 RemoveDirectory(targetPath);
 433             } catch (WindowsException ignore) { }
 434             throw x;
 435         }
 436 
 437         // copy security attributes. If this fails it doesn't cause the move
 438         // to fail.
 439         try {
 440             copySecurityAttributes(source, target, false);
 441         } catch (IOException ignore) { }
 442 
 443         // delete source
 444         try {
 445             RemoveDirectory(sourcePath);
 446         } catch (WindowsException x) {
 447             // rollback
 448             try {
 449                 RemoveDirectory(targetPath);
 450             } catch (WindowsException ignore) { }
 451             // ERROR_ALREADY_EXISTS is returned when attempting to delete
 452             // non-empty directory on SAMBA servers.
 453             if (x.lastError() == ERROR_DIR_NOT_EMPTY ||
 454                 x.lastError() == ERROR_ALREADY_EXISTS)
 455             {
 456                 throw new DirectoryNotEmptyException(
 457                     target.getPathForExceptionMessage());
 458             }
 459             x.rethrowAsIOException(source);
 460         }
 461     }
 462 
 463 
 464     private static String asWin32Path(WindowsPath path) throws IOException {
 465         try {
 466             return path.getPathForWin32Calls();
 467         } catch (WindowsException x) {
 468             x.rethrowAsIOException(path);
 469             return null;
 470         }
 471     }
 472 
 473     /**
 474      * Copy DACL/owner/group from source to target
 475      */
 476     private static void copySecurityAttributes(WindowsPath source,
 477                                                WindowsPath target,
 478                                                boolean followLinks)
 479         throws IOException
 480     {
 481         String path = WindowsLinkSupport.getFinalPath(source, followLinks);
 482 
 483         // may need SeRestorePrivilege to set file owner
 484         WindowsSecurity.Privilege priv =
 485             WindowsSecurity.enablePrivilege("SeRestorePrivilege");
 486         try {
 487             int request = (DACL_SECURITY_INFORMATION |
 488                 OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION);
 489             NativeBuffer buffer =
 490                 WindowsAclFileAttributeView.getFileSecurity(path, request);
 491             try {
 492                 try {
 493                     SetFileSecurity(target.getPathForWin32Calls(), request,
 494                         buffer.address());
 495                 } catch (WindowsException x) {
 496                     x.rethrowAsIOException(target);
 497                 }
 498             } finally {
 499                 buffer.release();
 500             }
 501         } finally {
 502             priv.drop();
 503         }
 504     }
 505 
 506     /**
 507      * Add long path prefix to path if required
 508      */
 509     private static String addPrefixIfNeeded(String path) {
 510         if (path.length() > 248) {
 511             if (path.startsWith("\\\\")) {
 512                 path = "\\\\?\\UNC" + path.substring(1, path.length());
 513             } else {
 514                 path = "\\\\?\\" + path;
 515             }
 516         }
 517         return path;
 518     }
 519 }