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.io.IOException; 29 import java.nio.file.AtomicMoveNotSupportedException; 30 import java.nio.file.CopyOption; 31 import java.nio.file.DirectoryNotEmptyException; 32 import java.nio.file.FileAlreadyExistsException; 33 import java.nio.file.LinkOption; 34 import java.nio.file.LinkPermission; 35 import java.nio.file.StandardCopyOption; 36 import java.security.AccessController; 37 import java.security.PrivilegedAction; 38 import java.util.concurrent.ExecutionException; 39 import java.util.concurrent.TimeUnit; 40 41 import static sun.nio.fs.UnixNativeDispatcher.*; 42 import static sun.nio.fs.UnixConstants.*; 43 44 45 /** 46 * Unix implementation of Path#copyTo and Path#moveTo methods. 47 */ 48 49 class UnixCopyFile { 50 private UnixCopyFile() { } 51 52 // The flags that control how a file is copied or moved 53 private static class Flags { 54 boolean replaceExisting; 55 boolean atomicMove; 56 boolean followLinks; 57 boolean interruptible; 58 59 // the attributes to copy 60 boolean copyBasicAttributes; 61 boolean copyPosixAttributes; 62 boolean copyNonPosixAttributes; 63 64 // flags that indicate if we should fail if attributes cannot be copied 65 boolean failIfUnableToCopyBasic; 66 boolean failIfUnableToCopyPosix; 67 boolean failIfUnableToCopyNonPosix; 68 69 static Flags fromCopyOptions(CopyOption... options) { 70 Flags flags = new Flags(); 71 flags.followLinks = true; 72 for (CopyOption option: options) { 73 if (option == StandardCopyOption.REPLACE_EXISTING) { 74 flags.replaceExisting = true; 75 continue; 76 } 77 if (option == LinkOption.NOFOLLOW_LINKS) { 78 flags.followLinks = false; 79 continue; 80 } 81 if (option == StandardCopyOption.COPY_ATTRIBUTES) { 82 // copy all attributes but only fail if basic attributes 83 // cannot be copied 84 flags.copyBasicAttributes = true; 85 flags.copyPosixAttributes = true; 86 flags.copyNonPosixAttributes = true; 87 flags.failIfUnableToCopyBasic = true; 88 continue; 89 } 90 if (ExtendedOptions.INTERRUPTIBLE.matches(option)) { 91 flags.interruptible = true; 92 continue; 93 } 94 if (option == null) 95 throw new NullPointerException(); 96 throw new UnsupportedOperationException("Unsupported copy option"); 97 } 98 return flags; 99 } 100 101 static Flags fromMoveOptions(CopyOption... options) { 102 Flags flags = new Flags(); 103 for (CopyOption option: options) { 104 if (option == StandardCopyOption.ATOMIC_MOVE) { 105 flags.atomicMove = true; 106 continue; 107 } 108 if (option == StandardCopyOption.REPLACE_EXISTING) { 109 flags.replaceExisting = true; 110 continue; 111 } 112 if (option == LinkOption.NOFOLLOW_LINKS) { 113 // ignore 114 continue; 115 } 116 if (option == null) 117 throw new NullPointerException(); 118 throw new UnsupportedOperationException("Unsupported copy option"); 119 } 120 121 // a move requires that all attributes be copied but only fail if 122 // the basic attributes cannot be copied 123 flags.copyBasicAttributes = true; 124 flags.copyPosixAttributes = true; 125 flags.copyNonPosixAttributes = true; 126 flags.failIfUnableToCopyBasic = true; 127 return flags; 128 } 129 } 130 131 // copy directory from source to target 132 private static void copyDirectory(UnixPath source, 133 UnixFileAttributes attrs, 134 UnixPath target, 135 Flags flags) 136 throws IOException 137 { 138 try { 139 mkdir(target, attrs.mode()); 140 } catch (UnixException x) { 141 x.rethrowAsIOException(target); 142 } 143 144 // no attributes to copy 145 if (!flags.copyBasicAttributes && 146 !flags.copyPosixAttributes && 147 !flags.copyNonPosixAttributes) return; 148 149 // open target directory if possible (this can fail when copying a 150 // directory for which we don't have read access). 151 int dfd = -1; 152 try { 153 dfd = open(target, O_RDONLY, 0); 154 } catch (UnixException x) { 155 // access to target directory required to copy named attributes 156 if (flags.copyNonPosixAttributes && flags.failIfUnableToCopyNonPosix) { 157 try { rmdir(target); } catch (UnixException ignore) { } 158 x.rethrowAsIOException(target); 159 } 160 } 161 162 boolean done = false; 163 try { 164 // copy owner/group/permissions 165 if (flags.copyPosixAttributes){ 166 try { 167 if (dfd >= 0) { 168 fchown(dfd, attrs.uid(), attrs.gid()); 169 fchmod(dfd, attrs.mode()); 170 } else { 171 chown(target, attrs.uid(), attrs.gid()); 172 chmod(target, attrs.mode()); 173 } 174 } catch (UnixException x) { 175 // unable to set owner/group 176 if (flags.failIfUnableToCopyPosix) 177 x.rethrowAsIOException(target); 178 } 179 } 180 // copy other attributes 181 if (flags.copyNonPosixAttributes && (dfd >= 0)) { 182 int sfd = -1; 183 try { 184 sfd = open(source, O_RDONLY, 0); 185 } catch (UnixException x) { 186 if (flags.failIfUnableToCopyNonPosix) 187 x.rethrowAsIOException(source); 188 } 189 if (sfd >= 0) { 190 source.getFileSystem().copyNonPosixAttributes(sfd, dfd); 191 close(sfd); 192 } 193 } 194 // copy time stamps last 195 if (flags.copyBasicAttributes) { 196 try { 197 if (dfd >= 0 && futimesSupported()) { 198 futimes(dfd, 199 attrs.lastAccessTime().to(TimeUnit.MICROSECONDS), 200 attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS)); 201 } else { 202 utimes(target, 203 attrs.lastAccessTime().to(TimeUnit.MICROSECONDS), 204 attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS)); 205 } 206 } catch (UnixException x) { 207 // unable to set times 208 if (flags.failIfUnableToCopyBasic) 209 x.rethrowAsIOException(target); 210 } 211 } 212 done = true; 213 } finally { 214 if (dfd >= 0) 215 close(dfd); 216 if (!done) { 217 // rollback 218 try { rmdir(target); } catch (UnixException ignore) { } 219 } 220 } 221 } 222 223 // copy regular file from source to target 224 private static void copyFile(UnixPath source, 225 UnixFileAttributes attrs, 226 UnixPath target, 227 Flags flags, 228 long addressToPollForCancel) 229 throws IOException 230 { 231 int fi = -1; 232 try { 233 fi = open(source, O_RDONLY, 0); 234 } catch (UnixException x) { 235 x.rethrowAsIOException(source); 236 } 237 238 try { 239 // open new file 240 int fo = -1; 241 try { 242 fo = open(target, 243 (O_WRONLY | 244 O_CREAT | 245 O_EXCL), 246 attrs.mode()); 247 } catch (UnixException x) { 248 x.rethrowAsIOException(target); 249 } 250 251 // set to true when file and attributes copied 252 boolean complete = false; 253 try { 254 // transfer bytes to target file 255 try { 256 transfer(fo, fi, addressToPollForCancel); 257 } catch (UnixException x) { 258 x.rethrowAsIOException(source, target); 259 } 260 // copy owner/permissions 261 if (flags.copyPosixAttributes) { 262 try { 263 fchown(fo, attrs.uid(), attrs.gid()); 264 fchmod(fo, attrs.mode()); 265 } catch (UnixException x) { 266 if (flags.failIfUnableToCopyPosix) 267 x.rethrowAsIOException(target); 268 } 269 } 270 // copy non POSIX attributes (depends on file system) 271 if (flags.copyNonPosixAttributes) { 272 source.getFileSystem().copyNonPosixAttributes(fi, fo); 273 } 274 // copy time attributes 275 if (flags.copyBasicAttributes) { 276 try { 277 if (futimesSupported()) { 278 futimes(fo, 279 attrs.lastAccessTime().to(TimeUnit.MICROSECONDS), 280 attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS)); 281 } else { 282 utimes(target, 283 attrs.lastAccessTime().to(TimeUnit.MICROSECONDS), 284 attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS)); 285 } 286 } catch (UnixException x) { 287 if (flags.failIfUnableToCopyBasic) 288 x.rethrowAsIOException(target); 289 } 290 } 291 complete = true; 292 } finally { 293 close(fo); 294 295 // copy of file or attributes failed so rollback 296 if (!complete) { 297 try { 298 unlink(target); 299 } catch (UnixException ignore) { } 300 } 301 } 302 } finally { 303 close(fi); 304 } 305 } 306 307 // copy symbolic link from source to target 308 private static void copyLink(UnixPath source, 309 UnixFileAttributes attrs, 310 UnixPath target, 311 Flags flags) 312 throws IOException 313 { 314 byte[] linktarget = null; 315 try { 316 linktarget = readlink(source); 317 } catch (UnixException x) { 318 x.rethrowAsIOException(source); 319 } 320 try { 321 symlink(linktarget, target); 322 323 if (flags.copyPosixAttributes) { 324 try { 325 lchown(target, attrs.uid(), attrs.gid()); 326 } catch (UnixException x) { 327 // ignore since link attributes not required to be copied 328 } 329 } 330 } catch (UnixException x) { 331 x.rethrowAsIOException(target); 332 } 333 } 334 335 // copy special file from source to target 336 private static void copySpecial(UnixPath source, 337 UnixFileAttributes attrs, 338 UnixPath target, 339 Flags flags) 340 throws IOException 341 { 342 try { 343 mknod(target, attrs.mode(), attrs.rdev()); 344 } catch (UnixException x) { 345 x.rethrowAsIOException(target); 346 } 347 boolean done = false; 348 try { 349 if (flags.copyPosixAttributes) { 350 try { 351 chown(target, attrs.uid(), attrs.gid()); 352 chmod(target, attrs.mode()); 353 } catch (UnixException x) { 354 if (flags.failIfUnableToCopyPosix) 355 x.rethrowAsIOException(target); 356 } 357 } 358 if (flags.copyBasicAttributes) { 359 try { 360 utimes(target, 361 attrs.lastAccessTime().to(TimeUnit.MICROSECONDS), 362 attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS)); 363 } catch (UnixException x) { 364 if (flags.failIfUnableToCopyBasic) 365 x.rethrowAsIOException(target); 366 } 367 } 368 done = true; 369 } finally { 370 if (!done) { 371 try { unlink(target); } catch (UnixException ignore) { } 372 } 373 } 374 } 375 376 // throw a DirectoryNotEmpty exception if appropriate 377 static void ensureEmptyDir(UnixPath dir) throws IOException { 378 try { 379 long ptr = opendir(dir); 380 try (UnixDirectoryStream stream = 381 new UnixDirectoryStream(dir, ptr, e -> true)) { 382 if (stream.iterator().hasNext()) { 383 throw new DirectoryNotEmptyException( 384 dir.getPathForExceptionMessage()); 385 } 386 } 387 } catch (UnixException e) { 388 e.rethrowAsIOException(dir); 389 } 390 } 391 392 // move file from source to target 393 static void move(UnixPath source, UnixPath target, CopyOption... options) 394 throws IOException 395 { 396 // permission check 397 SecurityManager sm = System.getSecurityManager(); 398 if (sm != null) { 399 source.checkWrite(); 400 target.checkWrite(); 401 } 402 403 // translate options into flags 404 Flags flags = Flags.fromMoveOptions(options); 405 406 // handle atomic rename case 407 if (flags.atomicMove) { 408 try { 409 rename(source, target); 410 } catch (UnixException x) { 411 if (x.errno() == EXDEV) { 412 throw new AtomicMoveNotSupportedException( 413 source.getPathForExceptionMessage(), 414 target.getPathForExceptionMessage(), 415 x.errorString()); 416 } 417 x.rethrowAsIOException(source, target); 418 } 419 return; 420 } 421 422 // move using rename or copy+delete 423 UnixFileAttributes sourceAttrs = null; 424 UnixFileAttributes targetAttrs = null; 425 426 // get attributes of source file (don't follow links) 427 try { 428 sourceAttrs = UnixFileAttributes.get(source, false); 429 } catch (UnixException x) { 430 x.rethrowAsIOException(source); 431 } 432 433 // get attributes of target file (don't follow links) 434 try { 435 targetAttrs = UnixFileAttributes.get(target, false); 436 } catch (UnixException x) { 437 // ignore 438 } 439 boolean targetExists = (targetAttrs != null); 440 441 // if the target exists: 442 // 1. check if source and target are the same file 443 // 2. throw exception if REPLACE_EXISTING option is not set 444 // 3. delete target if REPLACE_EXISTING option set 445 if (targetExists) { 446 if (sourceAttrs.isSameFile(targetAttrs)) 447 return; // nothing to do as files are identical 448 if (!flags.replaceExisting) { 449 throw new FileAlreadyExistsException( 450 target.getPathForExceptionMessage()); 451 } 452 453 // attempt to delete target 454 try { 455 if (targetAttrs.isDirectory()) { 456 rmdir(target); 457 } else { 458 unlink(target); 459 } 460 } catch (UnixException x) { 461 // target is non-empty directory that can't be replaced. 462 if (targetAttrs.isDirectory() && 463 (x.errno() == EEXIST || x.errno() == ENOTEMPTY)) 464 { 465 throw new DirectoryNotEmptyException( 466 target.getPathForExceptionMessage()); 467 } 468 x.rethrowAsIOException(target); 469 } 470 } 471 472 // first try rename 473 try { 474 rename(source, target); 475 return; 476 } catch (UnixException x) { 477 if (x.errno() != EXDEV && x.errno() != EISDIR) { 478 x.rethrowAsIOException(source, target); 479 } 480 } 481 482 // copy source to target 483 if (sourceAttrs.isDirectory()) { 484 ensureEmptyDir(source); 485 copyDirectory(source, sourceAttrs, target, flags); 486 } else { 487 if (sourceAttrs.isSymbolicLink()) { 488 copyLink(source, sourceAttrs, target, flags); 489 } else { 490 if (sourceAttrs.isDevice()) { 491 copySpecial(source, sourceAttrs, target, flags); 492 } else { 493 copyFile(source, sourceAttrs, target, flags, 0L); 494 } 495 } 496 } 497 498 // delete source 499 try { 500 if (sourceAttrs.isDirectory()) { 501 rmdir(source); 502 } else { 503 unlink(source); 504 } 505 } catch (UnixException x) { 506 // file was copied but unable to unlink the source file so attempt 507 // to remove the target and throw a reasonable exception 508 try { 509 if (sourceAttrs.isDirectory()) { 510 rmdir(target); 511 } else { 512 unlink(target); 513 } 514 } catch (UnixException ignore) { } 515 516 if (sourceAttrs.isDirectory() && 517 (x.errno() == EEXIST || x.errno() == ENOTEMPTY)) 518 { 519 throw new DirectoryNotEmptyException( 520 source.getPathForExceptionMessage()); 521 } 522 x.rethrowAsIOException(source); 523 } 524 } 525 526 // copy file from source to target 527 static void copy(final UnixPath source, 528 final UnixPath target, 529 CopyOption... options) throws IOException 530 { 531 // permission checks 532 SecurityManager sm = System.getSecurityManager(); 533 if (sm != null) { 534 source.checkRead(); 535 target.checkWrite(); 536 } 537 538 // translate options into flags 539 final Flags flags = Flags.fromCopyOptions(options); 540 541 UnixFileAttributes sourceAttrs = null; 542 UnixFileAttributes targetAttrs = null; 543 544 // get attributes of source file 545 try { 546 sourceAttrs = UnixFileAttributes.get(source, flags.followLinks); 547 } catch (UnixException x) { 548 x.rethrowAsIOException(source); 549 } 550 551 // if source file is symbolic link then we must check LinkPermission 552 if (sm != null && sourceAttrs.isSymbolicLink()) { 553 sm.checkPermission(new LinkPermission("symbolic")); 554 } 555 556 // get attributes of target file (don't follow links) 557 try { 558 targetAttrs = UnixFileAttributes.get(target, false); 559 } catch (UnixException x) { 560 // ignore 561 } 562 boolean targetExists = (targetAttrs != null); 563 564 // if the target exists: 565 // 1. check if source and target are the same file 566 // 2. throw exception if REPLACE_EXISTING option is not set 567 // 3. try to unlink the target 568 if (targetExists) { 569 if (sourceAttrs.isSameFile(targetAttrs)) 570 return; // nothing to do as files are identical 571 if (!flags.replaceExisting) 572 throw new FileAlreadyExistsException( 573 target.getPathForExceptionMessage()); 574 try { 575 if (targetAttrs.isDirectory()) { 576 rmdir(target); 577 } else { 578 unlink(target); 579 } 580 } catch (UnixException x) { 581 // target is non-empty directory that can't be replaced. 582 if (targetAttrs.isDirectory() && 583 (x.errno() == EEXIST || x.errno() == ENOTEMPTY)) 584 { 585 throw new DirectoryNotEmptyException( 586 target.getPathForExceptionMessage()); 587 } 588 x.rethrowAsIOException(target); 589 } 590 } 591 592 // do the copy 593 if (sourceAttrs.isDirectory()) { 594 copyDirectory(source, sourceAttrs, target, flags); 595 return; 596 } 597 if (sourceAttrs.isSymbolicLink()) { 598 copyLink(source, sourceAttrs, target, flags); 599 return; 600 } 601 if (!flags.interruptible) { 602 // non-interruptible file copy 603 copyFile(source, sourceAttrs, target, flags, 0L); 604 return; 605 } 606 607 // interruptible file copy 608 final UnixFileAttributes attrsToCopy = sourceAttrs; 609 Cancellable copyTask = new Cancellable() { 610 @Override public void implRun() throws IOException { 611 copyFile(source, attrsToCopy, target, flags, 612 addressToPollForCancel()); 613 } 614 }; 615 try { 616 Cancellable.runInterruptibly(copyTask); 617 } catch (ExecutionException e) { 618 Throwable t = e.getCause(); 619 if (t instanceof IOException) 620 throw (IOException)t; 621 throw new IOException(t); 622 } 623 } 624 625 // -- native methods -- 626 627 static native void transfer(int dst, int src, long addressToPollForCancel) 628 throws UnixException; 629 630 static { 631 AccessController.doPrivileged(new PrivilegedAction<>() { 632 @Override 633 public Void run() { 634 System.loadLibrary("nio"); 635 return null; 636 }}); 637 } 638 639 }