1 /*
   2  * Copyright (c) 2008, 2018, 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     // throw a DirectoryNotEmpty exception if not empty
 253     static void ensureEmptyDir(WindowsPath dir) throws IOException {
 254         try (WindowsDirectoryStream dirStream =
 255             new WindowsDirectoryStream(dir, (e) -> true)) {
 256             if (dirStream.iterator().hasNext()) {
 257                 throw new DirectoryNotEmptyException(
 258                     dir.getPathForExceptionMessage());
 259             }
 260         }
 261     }
 262 
 263     /**
 264      * Move file from source to target
 265      */
 266     static void move(WindowsPath source, WindowsPath target, CopyOption... options)
 267         throws IOException
 268     {
 269         // map options
 270         boolean atomicMove = false;
 271         boolean replaceExisting = false;
 272         for (CopyOption option: options) {
 273             if (option == StandardCopyOption.ATOMIC_MOVE) {
 274                 atomicMove = true;
 275                 continue;
 276             }
 277             if (option == StandardCopyOption.REPLACE_EXISTING) {
 278                 replaceExisting = true;
 279                 continue;
 280             }
 281             if (option == LinkOption.NOFOLLOW_LINKS) {
 282                 // ignore
 283                 continue;
 284             }
 285             if (option == null) throw new NullPointerException();
 286             throw new UnsupportedOperationException("Unsupported copy option");
 287         }
 288 
 289         SecurityManager sm = System.getSecurityManager();
 290         if (sm != null) {
 291             source.checkWrite();
 292             target.checkWrite();
 293         }
 294 
 295         final String sourcePath = asWin32Path(source);
 296         final String targetPath = asWin32Path(target);
 297 
 298         // atomic case
 299         if (atomicMove) {
 300             try {
 301                 MoveFileEx(sourcePath, targetPath, MOVEFILE_REPLACE_EXISTING);
 302             } catch (WindowsException x) {
 303                 if (x.lastError() == ERROR_NOT_SAME_DEVICE) {
 304                     throw new AtomicMoveNotSupportedException(
 305                         source.getPathForExceptionMessage(),
 306                         target.getPathForExceptionMessage(),
 307                         x.errorString());
 308                 }
 309                 x.rethrowAsIOException(source, target);
 310             }
 311             return;
 312         }
 313 
 314         // get attributes of source file
 315         // attempt to get attributes of target file
 316         // if both files are the same there is nothing to do
 317         // if target exists and !replace then throw exception
 318 
 319         WindowsFileAttributes sourceAttrs = null;
 320         WindowsFileAttributes targetAttrs = null;
 321 
 322         long sourceHandle = 0L;
 323         try {
 324             sourceHandle = source.openForReadAttributeAccess(false);
 325         } catch (WindowsException x) {
 326             x.rethrowAsIOException(source);
 327         }
 328         try {
 329             // source attributes
 330             try {
 331                 sourceAttrs = WindowsFileAttributes.readAttributes(sourceHandle);
 332             } catch (WindowsException x) {
 333                 x.rethrowAsIOException(source);
 334             }
 335 
 336             // open target (don't follow links)
 337             long targetHandle = 0L;
 338             try {
 339                 targetHandle = target.openForReadAttributeAccess(false);
 340                 try {
 341                     targetAttrs = WindowsFileAttributes.readAttributes(targetHandle);
 342 
 343                     // if both files are the same then nothing to do
 344                     if (WindowsFileAttributes.isSameFile(sourceAttrs, targetAttrs)) {
 345                         return;
 346                     }
 347 
 348                     // can't replace file
 349                     if (!replaceExisting) {
 350                         throw new FileAlreadyExistsException(
 351                             target.getPathForExceptionMessage());
 352                     }
 353 
 354                 } finally {
 355                     CloseHandle(targetHandle);
 356                 }
 357             } catch (WindowsException x) {
 358                 // ignore
 359             }
 360 
 361         } finally {
 362             CloseHandle(sourceHandle);
 363         }
 364 
 365         // if target exists then delete it.
 366         if (targetAttrs != null) {
 367             try {
 368                 if (targetAttrs.isDirectory() || targetAttrs.isDirectoryLink()) {
 369                     RemoveDirectory(targetPath);
 370                 } else {
 371                     DeleteFile(targetPath);
 372                 }
 373             } catch (WindowsException x) {
 374                 if (targetAttrs.isDirectory()) {
 375                     // ERROR_ALREADY_EXISTS is returned when attempting to delete
 376                     // non-empty directory on SAMBA servers.
 377                     if (x.lastError() == ERROR_DIR_NOT_EMPTY ||
 378                         x.lastError() == ERROR_ALREADY_EXISTS)
 379                     {
 380                         throw new DirectoryNotEmptyException(
 381                             target.getPathForExceptionMessage());
 382                     }
 383                 }
 384                 x.rethrowAsIOException(target);
 385             }
 386         }
 387 
 388         // first try MoveFileEx (no options). If target is on same volume then
 389         // all attributes (including security attributes) are preserved.
 390         try {
 391             MoveFileEx(sourcePath, targetPath, 0);
 392             return;
 393         } catch (WindowsException x) {
 394             if (x.lastError() != ERROR_NOT_SAME_DEVICE)
 395                 x.rethrowAsIOException(source, target);
 396         }
 397 
 398         // target is on different volume so use MoveFileEx with copy option
 399         if (!sourceAttrs.isDirectory() && !sourceAttrs.isDirectoryLink()) {
 400             try {
 401                 MoveFileEx(sourcePath, targetPath, MOVEFILE_COPY_ALLOWED);
 402             } catch (WindowsException x) {
 403                 x.rethrowAsIOException(source, target);
 404             }
 405             // MoveFileEx does not copy security attributes when moving
 406             // across volumes.
 407             try {
 408                 copySecurityAttributes(source, target, false);
 409             } catch (IOException x) {
 410                 // ignore
 411             }
 412             return;
 413         }
 414 
 415         // moving directory or directory-link to another file system
 416         assert sourceAttrs.isDirectory() || sourceAttrs.isDirectoryLink();
 417 
 418         // create new directory or directory junction
 419         try {
 420             if (sourceAttrs.isDirectory()) {
 421                 ensureEmptyDir(source);
 422                 CreateDirectory(targetPath, 0L);
 423             } else {
 424                 String linkTarget = WindowsLinkSupport.readLink(source);
 425                 CreateSymbolicLink(targetPath,
 426                                    WindowsPath.addPrefixIfNeeded(linkTarget),
 427                                    SYMBOLIC_LINK_FLAG_DIRECTORY);
 428             }
 429         } catch (WindowsException x) {
 430             x.rethrowAsIOException(target);
 431         }
 432 
 433         // copy timestamps/DOS attributes
 434         WindowsFileAttributeViews.Dos view =
 435                 WindowsFileAttributeViews.createDosView(target, false);
 436         try {
 437             view.setAttributes(sourceAttrs);
 438         } catch (IOException x) {
 439             // rollback
 440             try {
 441                 RemoveDirectory(targetPath);
 442             } catch (WindowsException ignore) { }
 443             throw x;
 444         }
 445 
 446         // copy security attributes. If this fails it doesn't cause the move
 447         // to fail.
 448         try {
 449             copySecurityAttributes(source, target, false);
 450         } catch (IOException ignore) { }
 451 
 452         // delete source
 453         try {
 454             RemoveDirectory(sourcePath);
 455         } catch (WindowsException x) {
 456             // rollback
 457             try {
 458                 RemoveDirectory(targetPath);
 459             } catch (WindowsException ignore) { }
 460             // ERROR_ALREADY_EXISTS is returned when attempting to delete
 461             // non-empty directory on SAMBA servers.
 462             if (x.lastError() == ERROR_DIR_NOT_EMPTY ||
 463                 x.lastError() == ERROR_ALREADY_EXISTS)
 464             {
 465                 throw new DirectoryNotEmptyException(
 466                     target.getPathForExceptionMessage());
 467             }
 468             x.rethrowAsIOException(source);
 469         }
 470     }
 471 
 472 
 473     private static String asWin32Path(WindowsPath path) throws IOException {
 474         try {
 475             return path.getPathForWin32Calls();
 476         } catch (WindowsException x) {
 477             x.rethrowAsIOException(path);
 478             return null;
 479         }
 480     }
 481 
 482     /**
 483      * Copy DACL/owner/group from source to target
 484      */
 485     private static void copySecurityAttributes(WindowsPath source,
 486                                                WindowsPath target,
 487                                                boolean followLinks)
 488         throws IOException
 489     {
 490         String path = WindowsLinkSupport.getFinalPath(source, followLinks);
 491 
 492         // may need SeRestorePrivilege to set file owner
 493         WindowsSecurity.Privilege priv =
 494             WindowsSecurity.enablePrivilege("SeRestorePrivilege");
 495         try {
 496             int request = (DACL_SECURITY_INFORMATION |
 497                 OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION);
 498             NativeBuffer buffer =
 499                 WindowsAclFileAttributeView.getFileSecurity(path, request);
 500             try {
 501                 try {
 502                     SetFileSecurity(target.getPathForWin32Calls(), request,
 503                         buffer.address());
 504                 } catch (WindowsException x) {
 505                     x.rethrowAsIOException(target);
 506                 }
 507             } finally {
 508                 buffer.release();
 509             }
 510         } finally {
 511             priv.drop();
 512         }
 513     }
 514 }