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