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