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 }