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 }