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