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 { WindowsFileCopy()40 private WindowsFileCopy() { 41 } 42 43 /** 44 * Copy file from source to target 45 */ copy(final WindowsPath source, final WindowsPath target, CopyOption... options)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 ensureEmptyDir(WindowsPath dir)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 */ move(WindowsPath source, WindowsPath target, CopyOption... options)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 asWin32Path(WindowsPath path)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 */ copySecurityAttributes(WindowsPath source, WindowsPath target, boolean followLinks)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 } 515